const webpack = require('webpack'); const chalk = require('chalk'); const path = require('path'); const fs = require('fs'); const resolve = require('resolve'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const StylelintPlugin = require('stylelint-webpack-plugin'); const ProgressBarPlugin = require('progress-bar-webpack-plugin'); const UiExtractPlugin = require('ui-extract-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('./../../plugins/ForkTsCheckerWarningWebpackPlugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const OakWeChatMpPlugin = require('../../plugins/WechatMpPlugin'); const getClientEnvironment = require('./env'); const paths = require('./paths'); const env = getClientEnvironment(); const pkg = require(paths.appPackageJson); // Check if TypeScript is setup const useTypeScript = fs.existsSync(paths.appTsConfig); const createEnvironmentHash = require('./webpack/persistentCache/createEnvironmentHash'); const oakI18nPlugin = require('../babel-plugin/oakI18n'); const oakPathPlugin = require('../babel-plugin/oakPath'); const oakGetAppVersionPlugin = require('../babel-plugin/oakGetAppVersion'); const reuseOakComponentPlugin = require('../babel-plugin/reuse-oak-component'); const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; const shouldAnalyze = process.env.COMPILE_ANALYZE === 'true'; const memoryLimit = process.env.MEMORY_LIMIT ? Number(process.env.MEMORY_LIMIT) : 8192; const copyPatterns = [].concat(pkg.copyWebpack || []).map((pattern) => typeof pattern === 'string' ? { from: path.resolve(paths.appSrc, pattern), to: path.resolve(paths.appBuild, pattern), } : pattern ); const oakRegex = /(\/*[a-zA-Z0-9_-])*\/(lib|src|es)\/|(\\*[a-zA-Z0-9_-])*\\(lib|src|es)\\/; module.exports = function (webpackEnv) { // 生产环境跟staging环境 mode不同,其他的配置应该一致 const isEnvStaging = webpackEnv === 'staging'; const isEnvDevelopment = webpackEnv === 'development'; const isEnvProduction = webpackEnv === 'production'; // 生产环境和staging都是none // 开发环境 使用eval-source-map const sourceMap = isEnvDevelopment ? 'cheap-module-source-map' : false; const oakFileLoader = (ext = '[ext]') => { return { loader: 'file-loader', options: { useRelativePath: true, name: `[path][name].${ext}`, outputPath: (url, resourcePath, context) => { const outputPath = url.replace(oakRegex, ''); return outputPath; }, context: paths.appSrc, }, }; }; // 读取编译配置 const compilerConfigurationFile = path.join( paths.appRootPath, 'configuration', 'compiler.js' ); const projectConfiguration = fs.existsSync(compilerConfigurationFile) && require(compilerConfigurationFile).webpack; const getOakInclude = () => { const result = [/oak-frontend-base/, /oak-general-business/]; if (projectConfiguration && projectConfiguration.extraOakModules) { result.push(...projectConfiguration.extraOakModules); } return result; }; return { target: ['web'], // Webpack noise constrained to errors and warnings stats: 'errors-warnings', mode: isEnvStaging ? 'none' : (isEnvProduction ? 'production' : 'development'), // Stop compilation early in production bail: !isEnvDevelopment, devtool: sourceMap, entry: { app: paths.appIndexJs, }, output: { path: paths.appBuild, filename: '[name].js', publicPath: paths.publicUrlOrPath, globalObject: 'global', }, resolve: { alias: (() => { const defaultAlias = { 'bn.js': require.resolve('bn.js'), assert: require.resolve('browser-assert'), }; if ( projectConfiguration && projectConfiguration.resolve && projectConfiguration.resolve.alias ) { Object.assign( defaultAlias, projectConfiguration.resolve.alias ); } return defaultAlias; })(), extensions: paths.moduleFileExtensions.map((ext) => `.${ext}`), symlinks: true, fallback: (() => { const defaultFb = { crypto: require.resolve('crypto-browserify'), buffer: require.resolve('safe-buffer'), stream: false, zlib: require.resolve('browserify-zlib'), events: require.resolve('events/'), querystring: require.resolve('querystring-es3'), url: false, path: false, fs: false, net: false, tls: false, }; if ( projectConfiguration && projectConfiguration.resolve && projectConfiguration.resolve.fallback ) { Object.assign( defaultFb, projectConfiguration.resolve.fallback ); } return defaultFb; })(), }, resolveLoader: { // 第一种方式选查找自己的loaders文件中有没有这个loader再查找node_modules文件 // modules: [path.resolve(__dirname, 'loaders'), 'node_modules'], // 第二种使用别名的方式引入自定义的loader alias: { 'wxml-loader': path.resolve( __dirname, '../loaders/wxml-loader.js' ), }, }, cache: { type: 'filesystem', version: createEnvironmentHash(env.raw), cacheDirectory: paths.appWebpackCache, store: 'pack', buildDependencies: { defaultWebpack: ['webpack/lib/'], config: [__filename], tsconfig: [paths.appTsConfig, paths.appJsConfig].filter((f) => fs.existsSync(f) ), }, }, infrastructureLogging: { level: 'none', }, optimization: { // 标记未被使用的代码 usedExports: true, // 删除 usedExports 标记的未使用的代码 minimize: !isEnvDevelopment, minimizer: [ new TerserPlugin({ extractComments: false, }), ], }, module: { rules: [ { test: /\.wxs$/, include: [paths.appSrc, paths.appRootSrc].concat( getOakInclude() ), type: 'javascript/auto', use: [oakFileLoader('wxs')], }, { test: /\.(png|jpg|gif|svg)$/, include: [paths.appSrc, paths.appRootSrc].concat( getOakInclude() ), type: 'javascript/auto', use: oakFileLoader(), }, { test: /\.wxss$/, include: [paths.appSrc, paths.appRootSrc].concat( getOakInclude() ), type: 'javascript/auto', use: [oakFileLoader('wxss')], }, { test: /\.less$/, include: [paths.appSrc, paths.appRootSrc].concat( getOakInclude() ), type: 'javascript/auto', use: [ oakFileLoader('wxss'), { loader: 'less-loader', options: { lessOptions: () => { const oakConfigJson = require(paths.oakConfigJson); return { javascriptEnabled: true, modifyVars: oakConfigJson.theme, }; }, }, }, ], }, { test: /\.js$/, include: [paths.appSrc, paths.appRootSrc].concat( getOakInclude() ), loader: 'babel-loader', options: { plugins: [oakI18nPlugin, oakPathPlugin, reuseOakComponentPlugin, oakGetAppVersionPlugin], }, }, { test: /\.ts$/, include: [paths.appSrc, paths.appRootSrc].concat( getOakInclude() ), use: [ { loader: 'babel-loader', options: { plugins: [oakI18nPlugin, oakPathPlugin], }, }, { loader: 'ts-loader', options: { configFile: paths.appTsConfig, context: paths.appRootPath, transpileOnly: true, }, }, ], }, { test: /\.xml$/, include: [paths.appSrc, paths.appRootSrc].concat( getOakInclude() ), type: 'javascript/auto', use: [ oakFileLoader('wxml'), { loader: 'wxml-loader', options: { appSrcPath: paths.appSrc, appRootPath: paths.appRootPath, appRootSrcPath: paths.appRootSrc, }, }, ], }, { test: /\.wxml$/, include: [paths.appSrc, paths.appRootSrc].concat( getOakInclude() ), type: 'javascript/auto', use: [oakFileLoader('wxml')], }, ...(projectConfiguration && projectConfiguration.module && projectConfiguration.module.rules ? projectConfiguration.module.rules : [] ), ], }, plugins: [ new UiExtractPlugin({ context: paths.appSrc }), new OakWeChatMpPlugin({ context: paths.appSrc, extensions: paths.moduleFileExtensions.map((ext) => `.${ext}`), exclude: [ '*/weui-miniprogram/*', '**/*.module.less', '**/*.native.less', '**/web.less', '**/locales/**/*', ], include: ['project.config.json', 'sitemap.json'], debugPanel: { name: 'oak-debugPanel', show: isEnvDevelopment, }, split: !isEnvDevelopment ? true : false, }), new webpack.DefinePlugin(env.stringified), new StylelintPlugin({ fix: true, files: '**/*.(sa|sc|le|wx|c)ss', context: paths.appSrc, }), new ProgressBarPlugin({ summary: false, format: ':msg :percent (:elapsed seconds)', customSummary: (buildTime) => console.log( chalk.gray(`\n[${new Date().toLocaleDateString()}`), chalk.green(`Compiled successfully!(${buildTime})\n`) ), }), // TypeScript type checking useTypeScript && new ForkTsCheckerWebpackPlugin({ async: isEnvDevelopment, typescript: { typescriptPath: resolve.sync('typescript', { basedir: paths.appNodeModules, }), configFile: paths.appTsConfig, context: paths.appRootPath, diagnosticOptions: { // semantic: true, syntactic: true, }, mode: 'write-references', // profile: true, memoryLimit, }, issue: { // This one is specifically to match during CI tests, // as micromatch doesn't match // otherwise. include: [ { file: '../**/app/**/*.ts' }, { file: '**/app/**/*.ts' }, { file: '../**/src/**/*.ts' }, { file: '**/src/**/*.ts' }, ], exclude: [ { file: '**/src/**/__tests__/**' }, { file: '**/src/**/?(*.){spec|test}.*' }, { file: '**/src/setupProxy.*' }, { file: '**/src/setupTests.*' }, ], }, logger: { infrastructure: 'silent', log: (message) => console.log(message), error: (message) => console.error(message), }, }), new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'], }), shouldAnalyze && new BundleAnalyzerPlugin({ // 可以是`server`,`static`或`disabled`。 // 在`server`模式下,分析器将启动HTTP服务器来显示软件包报告。 // 在“静态”模式下,会生成带有报告的单个HTML文件。 // 在`disabled`模式下,你可以使用这个插件来将`generateStatsFile`设置为`true`来生成Webpack Stats JSON文件。 analyzerMode: 'server', // 将在“服务器”模式下使用的主机启动HTTP服务器。 analyzerHost: '127.0.0.1', // 将在“服务器”模式下使用的端口启动HTTP服务器。 analyzerPort: 8889, // 路径捆绑,将在`static`模式下生成的报告文件。 // 相对于捆绑输出目录。 reportFilename: 'report.html', // 模块大小默认显示在报告中。 // 应该是`stat`,`parsed`或者`gzip`中的一个。 // 有关更多信息,请参见“定义”一节。 defaultSizes: 'parsed', // 在默认浏览器中自动打开报告 openAnalyzer: true, // 如果为true,则Webpack Stats JSON文件将在bundle输出目录中生成 generateStatsFile: false, // 如果`generateStatsFile`为`true`,将会生成Webpack Stats JSON文件的名字。 // 相对于捆绑输出目录。 statsFilename: 'stats.json', // stats.toJson()方法的选项。 // 例如,您可以使用`source:false`选项排除统计文件中模块的来源。 // 在这里查看更多选项:https: //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21 statsOptions: null, logLevel: 'info', }), copyPatterns.length > 0 && new CopyWebpackPlugin({ patterns: copyPatterns, }), , ].filter(Boolean), watch: isEnvDevelopment, watchOptions: { aggregateTimeout: 600, ignored: '**/node_modules', followSymlinks: true, ...(projectConfiguration?.watchOptions || {}), }, performance: { hints: false, }, }; };