Allow webpack.config to be authored in TypeScript using webpack's new language configuration features #459

Closed
opened 2025-08-09 17:16:22 +00:00 by fergalmoran · 0 comments
Owner

Originally created by @buvinghausen on 8/30/2017

webpack supports this via ts-node

We all know webpack configuration is an absolute nightmare between JavaScript's lack of a typing system combined with the fact that they still let you do all the ancient 1.x configuration. Via TypeScript we can leverage the typing system and best of all go through the types package and completely rip out all the 1.x configuration items to force everyone into using the new configuration items. I have it all working marvelously with webpack but the aspnet-webpack module expects it to be strictly in JavaScript which requires an extra compilation and transpilation step that I don't need anywhere else. Given Microsoft's commitment to TypeScript as the future of JavaScript I think this feature should be baked in as it will vastly help .NET developers be productive with configuring webpack.

import * as autoprefixer from "autoprefixer";
import * as CleanWebpackPlugin from "clean-webpack-plugin";
import * as cssnano from "cssnano";
import * as ExtractTextWebpackPlugin from "extract-text-webpack-plugin";
import * as path from "path";
import * as postcssFixes from "postcss-fixes";
import * as webpack from "webpack";
import * as webpackMerge from "webpack-merge";

export interface IBaseModule extends webpack.BaseModule {
  preLoaders?: IRule[];
  postLoaders?: IRule[];
}

export interface IModule extends IBaseModule {
  rules: IRule[];
}

export interface IBaseRule extends webpack.BaseRule {
  rules?: IRule[];
  oneOf?: IRule[];
}

export interface IBaseDirectRule extends IBaseRule {
  test: webpack.Condition | webpack.Condition[];
}

export interface IBaseSingleLoaderRule extends IBaseDirectRule {
  loader: webpack.NewLoader;
}

export interface ILoaderRule extends IBaseSingleLoaderRule {
  options?: { [name: string]: any };
}

export interface IUseRule extends IBaseDirectRule {
  use: webpack.NewLoader | webpack.NewLoader[];
}

export interface IRulesRule extends IBaseRule {
  rules: IRule[];
}

export interface IOneOfRule extends IBaseRule {
  oneOf: IRule[];
}

export type IRule = ILoaderRule | IUseRule | IRulesRule | IOneOfRule;

export interface IConfig extends webpack.Configuration {
  module?: IModule;
  resolve?: webpack.NewResolve;
  resolveLoader?: webpack.NewResolveLoader;
}

module.exports = () => {
  const isProdBuild: boolean = process.argv.indexOf("-p") !== -1;
  const outputFolder: string = "./src/wwwroot/dist";
  const tsConfigFile: string = "./config/tsconfig.json";
  const fileName: string = "[name].[ext]";
  const fontFolder: string = `fonts/${fileName}`;
  const imageFolder: string = `media/[ext]/${fileName}`;
  const urlLimit: number = 10000;

  const sharedConfig = (): IConfig  => ({
    devtool: isProdBuild ? "source-map" : "eval-source-map",
    module: {
      rules: [
        {
          enforce: "pre",
          test: /\.(tsx|ts)?$/,
          use: {
            loader: "tslint-loader",
            options: {
              emitErrors: isProdBuild,
              tsConfigFile,
            },
          },
        },
        {
          test: /\.(tsx|ts)?$/,
          use: {
            loader: "awesome-typescript-loader",
            options: {
              configFileName: tsConfigFile,
            },
          },
        },
        {
          enforce: "pre",
          test: /\.js$/,
          use: {
            loader: "source-map-loader",
          },
        },
        {
          test: /\.(jpe?g|png|gif)$/i,
          use: {
            loader: "file-loader",
            options: {
              name: imageFolder,
            },
          },
        },
        {
          test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
          use: {
            loader: "url-loader",
            options: {
              limit: urlLimit,
              mimetype: "image/svg+xml",
              name: imageFolder,
            },
          },
        },
      ],
    },
    output: {
      filename: "js/[name].js",
      path: path.resolve(__dirname, outputFolder),
      publicPath: "/dist/",
    },
    plugins: [
      new webpack.NoEmitOnErrorsPlugin(),
    ],
    resolve: {
      extensions: [".ts", ".tsx", ".js"],
    },
  });

  const clientConfig = webpackMerge(sharedConfig(), {
    entry: {
      app: "./src/client.tsx",
    },
    module: {
      rules: [
        {
          test: /(\.css|\.scss|\.sass)$/,
          use: ExtractTextWebpackPlugin.extract({
            fallback: "style-loader",
            use: [{
              loader: "css-loader",
              options: {
                importLoaders: 1,
                sourceMap: true,
              }}, {
              loader: "postcss-loader",
              options: {
                plugins: [
                  autoprefixer({
                    browsers: ["last 2 versions"],
                  }),
                  postcssFixes({
                    preset: "recommended",
                  }),
                ].concat(isProdBuild ? [
                  cssnano({
                    calc: false,
                    safe: true,
                  }),
                ] : []),
                sourceMap: true,
              }}, {
              loader: "sass-loader",
              options: {
                sourceMap: true,
              },
            }],
          }),
        },
        {
          test: /\.eot(\?v=\d+.\d+.\d+)?$/,
          use: {
            loader: "file-loader",
            options: {
              name: fontFolder,
            },
          },
        },
        {
          test: /\.ico$/,
          use: {
            loader: "file-loader",
            options: {
              name: fileName,
            },
          },
        },
        {
          test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
          use: {
            loader: "url-loader",
            options: {
              limit: urlLimit,
              mimetype: "application/font-woff",
              name: fontFolder,
            },
          },
        },
        {
          test: /\.[ot]tf(\?v=\d+.\d+.\d+)?$/,
          use: {
            loader: "url-loader",
            options: {
              limit: urlLimit,
              mimetype: "application/octet-stream",
              name: fontFolder,
            },
          },
        },
      ],
    },
    plugins: [
      new CleanWebpackPlugin([outputFolder]),
      new webpack.optimize.CommonsChunkPlugin({
        minChunks(module: IConfig) {
          return module.context && module.context.indexOf("node_modules") !== -1;
        },
        name: "vendor",
      }),
      new webpack.optimize.CommonsChunkPlugin({
        minChunks: Infinity,
        name: "manifest",
      }),
      new ExtractTextWebpackPlugin({
        allChunks: true,
        filename: "css/[name].css",
      }),
    ],
    target: "web",
  } as IConfig);

  const serverConfig = webpackMerge(sharedConfig(), {
    entry: {
      server: "./src/server.tsx",
    },
    output: {
      libraryTarget: "commonjs",
    },
    resolve: {
      mainFields: ["main"],
    },
    target: "node",
  } as IConfig);

  return [ clientConfig, serverConfig ];
};
*Originally created by @buvinghausen on 8/30/2017* [webpack supports this via ts-node](https://webpack.js.org/configuration/configuration-languages/#typescript) We all know webpack configuration is an absolute nightmare between JavaScript's lack of a typing system combined with the fact that they still let you do all the ancient 1.x configuration. Via TypeScript we can leverage the typing system and best of all go through the types package and completely rip out all the 1.x configuration items to force everyone into using the new configuration items. I have it all working marvelously with webpack but the aspnet-webpack module expects it to be strictly in JavaScript which requires an extra compilation and transpilation step that I don't need anywhere else. Given Microsoft's commitment to TypeScript as the future of JavaScript I think this feature should be baked in as it will vastly help .NET developers be productive with configuring webpack. ```TypeScript import * as autoprefixer from "autoprefixer"; import * as CleanWebpackPlugin from "clean-webpack-plugin"; import * as cssnano from "cssnano"; import * as ExtractTextWebpackPlugin from "extract-text-webpack-plugin"; import * as path from "path"; import * as postcssFixes from "postcss-fixes"; import * as webpack from "webpack"; import * as webpackMerge from "webpack-merge"; export interface IBaseModule extends webpack.BaseModule { preLoaders?: IRule[]; postLoaders?: IRule[]; } export interface IModule extends IBaseModule { rules: IRule[]; } export interface IBaseRule extends webpack.BaseRule { rules?: IRule[]; oneOf?: IRule[]; } export interface IBaseDirectRule extends IBaseRule { test: webpack.Condition | webpack.Condition[]; } export interface IBaseSingleLoaderRule extends IBaseDirectRule { loader: webpack.NewLoader; } export interface ILoaderRule extends IBaseSingleLoaderRule { options?: { [name: string]: any }; } export interface IUseRule extends IBaseDirectRule { use: webpack.NewLoader | webpack.NewLoader[]; } export interface IRulesRule extends IBaseRule { rules: IRule[]; } export interface IOneOfRule extends IBaseRule { oneOf: IRule[]; } export type IRule = ILoaderRule | IUseRule | IRulesRule | IOneOfRule; export interface IConfig extends webpack.Configuration { module?: IModule; resolve?: webpack.NewResolve; resolveLoader?: webpack.NewResolveLoader; } module.exports = () => { const isProdBuild: boolean = process.argv.indexOf("-p") !== -1; const outputFolder: string = "./src/wwwroot/dist"; const tsConfigFile: string = "./config/tsconfig.json"; const fileName: string = "[name].[ext]"; const fontFolder: string = `fonts/${fileName}`; const imageFolder: string = `media/[ext]/${fileName}`; const urlLimit: number = 10000; const sharedConfig = (): IConfig => ({ devtool: isProdBuild ? "source-map" : "eval-source-map", module: { rules: [ { enforce: "pre", test: /\.(tsx|ts)?$/, use: { loader: "tslint-loader", options: { emitErrors: isProdBuild, tsConfigFile, }, }, }, { test: /\.(tsx|ts)?$/, use: { loader: "awesome-typescript-loader", options: { configFileName: tsConfigFile, }, }, }, { enforce: "pre", test: /\.js$/, use: { loader: "source-map-loader", }, }, { test: /\.(jpe?g|png|gif)$/i, use: { loader: "file-loader", options: { name: imageFolder, }, }, }, { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, use: { loader: "url-loader", options: { limit: urlLimit, mimetype: "image/svg+xml", name: imageFolder, }, }, }, ], }, output: { filename: "js/[name].js", path: path.resolve(__dirname, outputFolder), publicPath: "/dist/", }, plugins: [ new webpack.NoEmitOnErrorsPlugin(), ], resolve: { extensions: [".ts", ".tsx", ".js"], }, }); const clientConfig = webpackMerge(sharedConfig(), { entry: { app: "./src/client.tsx", }, module: { rules: [ { test: /(\.css|\.scss|\.sass)$/, use: ExtractTextWebpackPlugin.extract({ fallback: "style-loader", use: [{ loader: "css-loader", options: { importLoaders: 1, sourceMap: true, }}, { loader: "postcss-loader", options: { plugins: [ autoprefixer({ browsers: ["last 2 versions"], }), postcssFixes({ preset: "recommended", }), ].concat(isProdBuild ? [ cssnano({ calc: false, safe: true, }), ] : []), sourceMap: true, }}, { loader: "sass-loader", options: { sourceMap: true, }, }], }), }, { test: /\.eot(\?v=\d+.\d+.\d+)?$/, use: { loader: "file-loader", options: { name: fontFolder, }, }, }, { test: /\.ico$/, use: { loader: "file-loader", options: { name: fileName, }, }, }, { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, use: { loader: "url-loader", options: { limit: urlLimit, mimetype: "application/font-woff", name: fontFolder, }, }, }, { test: /\.[ot]tf(\?v=\d+.\d+.\d+)?$/, use: { loader: "url-loader", options: { limit: urlLimit, mimetype: "application/octet-stream", name: fontFolder, }, }, }, ], }, plugins: [ new CleanWebpackPlugin([outputFolder]), new webpack.optimize.CommonsChunkPlugin({ minChunks(module: IConfig) { return module.context && module.context.indexOf("node_modules") !== -1; }, name: "vendor", }), new webpack.optimize.CommonsChunkPlugin({ minChunks: Infinity, name: "manifest", }), new ExtractTextWebpackPlugin({ allChunks: true, filename: "css/[name].css", }), ], target: "web", } as IConfig); const serverConfig = webpackMerge(sharedConfig(), { entry: { server: "./src/server.tsx", }, output: { libraryTarget: "commonjs", }, resolve: { mainFields: ["main"], }, target: "node", } as IConfig); return [ clientConfig, serverConfig ]; }; ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/JavaScriptServices#459
No description provided.