Trying to get Typescript to work with Phoenix 1.4.0 dev

I’m now trying to start a new project with Phoenix 1.4.0 as it comes with Webpack by default, and I’m trying to get Typescript to work with it, which is surprisingly difficult.

I changed the file app.js to app.ts, and the problem I’m struggling with now is the first statement: import css from '../css/app.css';. Apparently, from questions such as https://stackoverflow.com/questions/41336858/how-to-import-css-modules-with-typescript-react-and-webpack, and https://stackoverflow.com/questions/40382842/cant-import-css-scss-modules-typescript-says-cannot-find-module, it’s a nontrivial task to import CSS modules in Typescript… Typescript wouldn’t recognize the module which doesn’t end in .js or .ts.

According to the instructions in that post, I tried to create a file called typings.d.ts:

declare module '*.css' {
  interface IClassNames {
    [className: string]: string;
  }
  const classNames: IClassNames;
  export = classNames;
}

Now, Typescript doesn’t complain about the module. However, running npx webpack --mode development doesn’t actually emit the CSS file whatsoever. webpack just completely ignores the app.css file.

The following is my webpack.config.js. I only changed two places:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = (env, options) => ({
  optimization: {
    minimizer: [new UglifyJsPlugin({ cache: true, parallel: true, sourceMap: false }), new OptimizeCSSAssetsPlugin({})]
  },
  // entry: './js/app.js',
  entry: './js/app.ts',
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, '../priv/static/js')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: 'ts-loader'
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({ filename: '../css/app.css' }),
    new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
  ]
});

I think this should be fairly easy to replicate with a new project.

  • cd assets
  • npm install --save-dev typescript ts-loader
  • npx tsc init
  • Create typings.d.ts in the folder
  • Edit webpack.config.js

I think the problem might lie with the Typescript module declaration. Unfortunately I don’t know enough to come up with a more sensible solution. I tried to generate a module for each css file in the /css folder with https://github.com/Quramy/typed-css-modules, but Typescript complains that assets/css/app.css.d.ts' is not a module.

Should I just give up the idea of turning app.js into a Typescript file and try to integrate Typescript in some other fashion, e.g. wrapping it in a module which is to be imported in app.js?

2 Likes

Well somehow if I try to put the JS definitions in another file under /js folder, Typescript complains about not being able to find the module, even though there’s no problem in VSCode IntelliSense…

EDIT: It actually worked if I specify the .ts suffix. So keeping app.js pure Javascript seems to be an option…

A workaround that worked is to require app.css instead of importing:

declare function require(name: string): string;
const css = require('../css/app.css');

However, it really feels clumsy.

This seems to mention that you need a tsconfig.json (which you don’t list).

tsc init creates a tsconfig.json.

And it contains … ?

See also
https://medium.com/@sapegin/css-modules-with-typescript-and-webpack-6b221ebe5f10

Sorry, misread your reply in a hurry. I think the contents conform to the guides as well:

{
  "compilerOptions": {
    "target":      "es5",
    "module":      "commonjs",
    "strict": true, 
    "esModuleInterop": true
  }
}

It seems that importing CSS modules is inherently thorny with Typescript… The two workarounds that worked:

  • Keep app.js in Javascript. Then when importing other modules defined in TS, one needs to append the .ts suffix to those modules.
  • Use Typescript for app.ts but write
    declare function require(name: string): string;
    const css = require('../css/app.css');
    
    instead of import css from '../css/app.css'; at the top of app.ts.

Thanks, I also tried that loader but it didn’t solve the issue either it seems… As I mentioned above, app.css can contain no CSS rules at all so the generation doesn’t seem to succeed.

FYI: The POI documentation recommends this tsconfig.json:

{
  "compilerOptions": {
    // this aligns with Vue's browser support
    "target": "es5",
    // this enables stricter inference for data properties on `this`
    "strict": true,
    // if using webpack 2+ or rollup, to leverage tree shaking:
    "module": "es2015",
    "moduleResolution": "node"
  }
}

Furthermore in Zero config React + Typescript + CSS Modules + JEST with Poi (2018-02-18):

Let’s get that fixed too and add a global.d.ts file with the following line in it:
declare module '*.css';

3 Likes