/// import path from 'path'; import {readdirSync, readFileSync} from 'fs'; import webpack, {Configuration} from 'webpack'; import SizePlugin from 'size-plugin'; import TerserPlugin from 'terser-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; function parseFeatureDetails(name: string): FeatureInfo { const content = readFileSync(`source/features/${name}.tsx`, {encoding: 'utf-8'}); const fields = ['disabled', 'description', 'screenshot'] as const; const feature: Partial = {name}; for (const field of fields) { const value = new RegExp(`\n\t${field}: '([^\\n]+)'`).exec(content)?.[1]; if (value) { const validValue = value.trim().replace(/\\'/g, '’'); // Catch trailing spaces and incorrect apostrophes if (value !== validValue) { throw new Error(` Invalid characters found in \`${name}\`. Apply this patch: - ${field}: '${value}' + ${field}: '${validValue}' `); } feature[field] = value.replace(/\\\\/g, '\\'); } else if (field === 'description') { throw new Error(`Description wasn't found in the \`${name}\` feature`); } } return feature as FeatureInfo; } function getFeatures(): string[] { return readdirSync(path.join(__dirname, 'source/features')) .filter(filename => filename.endsWith('.tsx')) .map(filename => filename.replace('.tsx', '')); } const config: Configuration = { devtool: 'source-map', stats: { all: false, errors: true, builtAt: true }, entry: { content: './source/content', background: './source/background', options: './source/options', 'resolve-conflicts': './source/resolve-conflicts' }, output: { path: path.join(__dirname, 'distribution'), filename: '[name].js' }, module: { rules: [ { test: /\.tsx?$/, use: [ { loader: 'ts-loader', query: { compilerOptions: { // Enables ModuleConcatenation. It must be in here to avoid conflict with ts-node module: 'es2015' }, // Make compilation faster with `fork-ts-checker-webpack-plugin` transpileOnly: true } } ], exclude: /node_modules/ }, { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader' ] }, { // Allows us to import SVG as JSX modules test: /\.svg$/i, use: [ 'buble-loader', // Converts JSX to vanilla `React.createElement` calls because TypeScript can't handle JSX outside jsx/tsx files: https://github.com/microsoft/TypeScript/issues/10939 path.resolve(__dirname, 'octicon-svg-loader.ts') // Converts the SVG file into a JSX module with default export ] } ] }, plugins: [ new ForkTsCheckerWebpackPlugin(), new webpack.DefinePlugin({ // Passing `true` as the second argument makes these values dynamic — so every file change will update their value. // @ts-ignore __featuresOptionDefaults__: webpack.DefinePlugin.runtimeValue(() => { return JSON.stringify(getFeatures().reduce((defaults, feature) => { defaults[`feature:${feature}`] = true; return defaults; }, {} as AnyObject)); }, true), // @ts-ignore __featuresInfo__: webpack.DefinePlugin.runtimeValue(() => { return JSON.stringify(getFeatures().map(parseFeatureDetails)); }, true), // @ts-ignore __featureName__: webpack.DefinePlugin.runtimeValue(({module}) => { return JSON.stringify(path.basename(module.resource, '.tsx')); }) }), new MiniCssExtractPlugin({ filename: '[name].css' }), new SizePlugin({ writeFile: false }), new CopyWebpackPlugin([ { from: '*', context: 'source', ignore: [ '*.js', '*.ts', '*.tsx', '*.css' ] }, { from: 'node_modules/webextension-polyfill/dist/browser-polyfill.min.js' } ]) ], resolve: { alias: { octicon: '@primer/octicons/build/svg' }, extensions: [ '.tsx', '.ts', '.js' ] }, optimization: { // Without this, function names will be garbled and enableFeature won't work concatenateModules: true, // Automatically enabled on production; keeps it somewhat readable for AMO reviewers minimizer: [ new TerserPlugin({ parallel: true, terserOptions: { mangle: false, compress: false, output: { beautify: true, indent_level: 2 // eslint-disable-line @typescript-eslint/camelcase } } }) ] } }; export default config;