Allow webpack.config to be authored in TypeScript using webpack's new language configuration features #1241
Description
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 ];
};