import * as fs from 'fs-extra'; import * as HtmlWebpackPlugin from 'html-webpack-plugin'; // TODO: // import * as MiniCssExtractPlugin from 'mini-css-extract-plugin'; import * as path from 'path'; import * as webpack from 'webpack'; import { globalState } from '../utils/global-state'; import { plugin } from '../utils/plugins'; import { docsPath, pagesPath, srcPath, tempPath } from '../utils/structor-config'; import { babelOptions } from './babel-options'; interface IOptions { mode: 'development' | 'production'; entryPath: string | string[]; htmlTemplatePath?: string; htmlTemplateArgs?: { dashboardServerPort?: number; libraryStaticPath?: string; }; publicPath?: string; distDir?: string; outFileName?: string; outCssFileName?: string; } /** * Get webpack config. */ export const getWebpackConfig = async (opts: IOptions) => { /** * Mutilpe loaders */ const styleLoader = { loader: 'style-loader', options: plugin.buildConfigStyleLoaderOptionsPipes.reduce((options, fn) => fn(options), {}) }; const cssLoader = { loader: 'css-loader', options: plugin.buildConfigCssLoaderOptionsPipes.reduce((options, fn) => fn(options), {}) }; const sassLoader = { loader: 'sass-loader', options: plugin.buildConfigSassLoaderOptionsPipes.reduce((options, fn) => fn(options), {}) }; const lessLoader = { loader: 'less-loader', options: plugin.buildConfigLessLoaderOptionsPipes.reduce((options, fn) => fn(options), {}) }; const babelLoader = { loader: 'babel-loader', options: plugin.buildConfigBabelLoaderOptionsPipes.reduce((options, fn) => fn(options), babelOptions) }; const tsLoader = { loader: 'ts-loader', options: plugin.buildConfigTsLoaderOptionsPipes.reduce((options, fn) => fn(options), { happyPackMode: true }) }; /** * Helper */ function extraCssInProd(...loaders: any[]) { return [styleLoader, ...loaders]; // TODO: // if (globalState.isDevelopment) { // return [styleLoader, ...loaders]; // } else { // return [MiniCssExtractPlugin.loader, ...loaders]; // } } const distDir = opts.distDir || path.join(globalState.projectRootPath, globalState.projectConfig.distDir); const outFileName = opts.outFileName || globalState.projectConfig.outFileName; const outCssFileName = opts.outCssFileName || globalState.projectConfig.outCssFileName; let publicPath: string = opts.publicPath || globalState.projectConfig.publicPath || '/'; if (!publicPath.endsWith('/')) { publicPath += '/'; } const stats = { warnings: false, version: false, modules: false, entrypoints: false, hash: false }; const defaultSourcePathToBeResolve = [ path.join(globalState.projectRootPath, srcPath.dir), path.join(globalState.projectRootPath, tempPath.dir) ]; const config: webpack.Configuration = { mode: opts.mode, entry: opts.entryPath, devtool: opts.mode === 'development' ? 'source-map' : false, output: { path: distDir, filename: outFileName, publicPath, chunkFilename: '[name].[hash].chunk.js', hotUpdateChunkFilename: 'hot~[id].[hash].chunk.js', hotUpdateMainFilename: 'hot-update.[hash].json', hashDigestLength: 4 }, module: { rules: [ { test: /\.jsx?$/, use: [babelLoader], include: plugin.buildConfigJsLoaderIncludePipes.reduce( (options, fn) => fn(options), defaultSourcePathToBeResolve ), exclude: plugin.buildConfigJsLoaderExcludePipes.reduce((options, fn) => fn(options), []) }, { test: /\.tsx?$/, use: [babelLoader, tsLoader], include: plugin.buildConfigTsLoaderIncludePipes.reduce( (options, fn) => fn(options), defaultSourcePathToBeResolve ), exclude: plugin.buildConfigTsLoaderExcludePipes.reduce((options, fn) => fn(options), []) }, { test: /\.css$/, use: extraCssInProd(cssLoader) }, { test: /\.scss$/, use: extraCssInProd(cssLoader, sassLoader), include: plugin.buildConfigSassLoaderIncludePipes.reduce( (options, fn) => fn(options), defaultSourcePathToBeResolve ), exclude: plugin.buildConfigSassLoaderExcludePipes.reduce((options, fn) => fn(options), []) }, { test: /\.less$/, use: extraCssInProd(cssLoader, lessLoader), include: plugin.buildConfigLessLoaderIncludePipes.reduce( (options, fn) => fn(options), defaultSourcePathToBeResolve ), exclude: plugin.buildConfigLessLoaderExcludePipes.reduce((options, fn) => fn(options), []) }, { test: /\.html$/, use: ['raw-loader'] } ] }, resolve: { modules: [ // From project node_modules path.join(globalState.projectRootPath, 'node_modules'), path.join(__dirname, '../../node_modules') ], alias: { '@': path.join(globalState.projectRootPath, '/src') }, extensions: ['.js', '.jsx', '.tsx', '.ts', '.scss', '.less', '.css'] }, resolveLoader: { modules: [ // From project node_modules // Self node_modules path.join(globalState.projectRootPath, 'node_modules'), path.join(__dirname, '../../node_modules') ] }, plugins: [], optimization: { namedChunks: false }, stats }; if (globalState.isDevelopment) { if (opts.htmlTemplatePath) { config.plugins.push( new HtmlWebpackPlugin({ title: globalState.projectConfig.title || globalState.projectRootPath.split(path.sep).pop(), filename: 'index.html', template: opts.htmlTemplatePath, htmlTemplateArgs: opts.htmlTemplateArgs }) ); } } if (!globalState.isDevelopment) { config.optimization.splitChunks = { ...config.optimization.splitChunks, cacheGroups: { styles: { name: outCssFileName.replace(/\.css$/g, ''), test: /\.(s?css)$/, enforce: true } } }; // TODO: // config.plugins.push(new MiniCssExtractPlugin()); babelLoader.options.plugins.push(['import', { libraryName: 'antd' }]); cssLoader.options.minimize = true; } if (globalState.isDevelopment) { // Turn off performance hints during development. if (!config.performance) { config.performance = {}; } config.performance.hints = false; } return plugin.buildConfigPipes.reduce(async (newConfig, fn) => fn(await newConfig), Promise.resolve(config)); };