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 = process.env.TSC_COMPILE_ON_ERROR === 'true' ? require('./../../plugins/ForkTsCheckerWarningWebpackPlugin') : require('react-dev-utils/ForkTsCheckerWebpackPlugin'); 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 shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; 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_-])*\/app\/|(\\*[a-zA-Z0-9_-])*\\app\\/; const localRegex = /(\/*[a-zA-Z0-9_-])*\/src+\/|(\\*[a-zA-Z0-9_-])*\\src+\\/; module.exports = function (webpackEnv) { const isEnvDevelopment = webpackEnv === 'development'; const isEnvProduction = webpackEnv === 'production'; const relativeFileLoader = (ext = '[ext]') => { return { loader: 'file-loader', options: { useRelativePath: true, name: `[path][name].${ext}`, context: paths.appSrc, }, }; }; 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 localFileLoader = (ext = '[ext]') => { return { loader: 'file-loader', options: { useRelativePath: true, name: `[path][name].${ext}`, outputPath: (url, resourcePath, context) => { const outputPath = url.replace(localRegex, ''); return outputPath; }, context: paths.appSrc, }, }; }; const getOakInclude = () => { return isEnvProduction ? [/oak-general-business/] : [ /oak-domain/, /oak-external-sdk/, /oak-frontend-base/, /oak-general-business/, /oak-memory-tree-store/, /oak-common-aspect/, ]; }; return { target: ['web'], // Webpack noise constrained to errors and warnings stats: 'errors-warnings', mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', // Stop compilation early in production bail: isEnvProduction, devtool: isEnvProduction ? shouldUseSourceMap ? 'source-map' : false : isEnvDevelopment && 'cheap-module-source-map', entry: { app: paths.appIndexJs, }, output: { path: paths.appBuild, filename: '[name].js', publicPath: paths.publicUrlOrPath, globalObject: 'global', }, resolve: { alias: { assert: require.resolve('assert'), '@': paths.appSrc, '@project': paths.appRootSrc, '@oak-general-business': paths.oakGeneralBusinessAppPath, }, extensions: paths.moduleFileExtensions.map((ext) => `.${ext}`), symlinks: true, fallback: { crypto: require.resolve('crypto-browserify'), buffer: require.resolve('safe-buffer'), stream: require.resolve('stream-browserify'), }, }, resolveLoader: { // 第一种使用别名的方式引入自定义的loader alias: { 'wxml-loader': path.resolve( __dirname, '../loaders/wxml-loader.js' ), }, // 第二种方式选查找自己的loaders文件中有没有这个loader再查找node_modules文件 // modules: [path.resolve(__dirname, 'loaders'), 'node_modules'], }, 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: isEnvProduction, minimizer: [ new TerserPlugin({ extractComments: false, }), ], }, module: { rules: [ { test: /\.wxs$/, include: paths.appSrc, type: 'javascript/auto', use: [relativeFileLoader()], }, { test: /\.wxs$/, include: oakRegex, exclude: paths.appSrc, type: 'javascript/auto', use: [oakFileLoader('wxs')], }, { test: /\.wxs$/, include: paths.appRootSrc, type: 'javascript/auto', use: [oakFileLoader('wxs')], }, { test: /\.(png|jpg|gif|svg)$/, include: paths.appSrc, type: 'javascript/auto', use: relativeFileLoader(), }, { test: /\.(png|jpg|gif|svg)$/, include: oakRegex, exclude: paths.appSrc, type: 'javascript/auto', use: oakFileLoader(), }, { test: /\.(png|jpg|gif|svg)$/, include: paths.appRootSrc, type: 'javascript/auto', use: localFileLoader(), }, { test: /\.less$/, include: paths.appSrc, exclude: /node_modules/, use: [ relativeFileLoader('wxss'), { loader: 'less-loader', }, ], }, { test: /\.less$/, include: oakRegex, exclude: paths.appSrc, type: 'javascript/auto', use: [ oakFileLoader('wxss'), { loader: 'less-loader', options: { lessOptions: () => { const oakConfigJson = require(paths.oakConfigJson); return { javascriptEnabled: true, modifyVars: oakConfigJson.theme, }; }, }, }, ], }, { test: /\.less$/, include: paths.appRootSrc, type: 'javascript/auto', use: [ localFileLoader('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() ), exclude: /node_modules/, loader: 'babel-loader', }, { test: /\.((?!tsx)ts)$/, include: [paths.appSrc, paths.appRootSrc].concat( getOakInclude() ), exclude: /node_modules/, loader: 'ts-loader', options: { configFile: paths.appTsConfig, context: paths.appRootPath, transpileOnly: true, }, }, // { // test: /\.json$/, // include: paths.appSrc, // exclude: /node_modules/, // type: 'asset/resource', // generator: { // filename: `[path][name].[ext]`, // }, // // type: 'javascript/auto', // // use: [relativeFileLoader('json')], // }, { test: /\.(xml|wxml)$/, include: paths.appSrc, exclude: /node_modules/, type: 'javascript/auto', use: [ relativeFileLoader('wxml'), { loader: 'wxml-loader', options: { context: paths.appSrc, }, }, ], }, { test: /\.(xml|wxml)$/, include: oakRegex, exclude: paths.appSrc, type: 'javascript/auto', use: [ oakFileLoader('wxml'), { loader: 'wxml-loader', options: { context: paths.appSrc, }, }, ], }, { test: /\.(xml|wxml)$/, include: paths.appRootSrc, type: 'javascript/auto', use: [ localFileLoader('wxml'), { loader: 'wxml-loader', options: { context: paths.appSrc, }, }, ], }, ], }, plugins: [ new UiExtractPlugin({ context: paths.appSrc }), new OakWeChatMpPlugin({ context: paths.appSrc, extensions: paths.moduleFileExtensions.map((ext) => `.${ext}`), exclude: ['*/weui-miniprogram/*'], include: ['project.config.json', 'sitemap.json'], split: !isEnvDevelopment, }), 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, }), // configOverwrite: { // compilerOptions: { // sourceMap: isEnvProduction // ? shouldUseSourceMap // : isEnvDevelopment, // skipLibCheck: true, // inlineSourceMap: false, // declarationMap: false, // noEmit: true, // incremental: true, // tsBuildInfoFile: paths.appTsBuildInfoFile, // }, // }, configFile: paths.appTsConfig, context: paths.appRootPath, diagnosticOptions: { // semantic: true, syntactic: true, }, mode: 'write-references', // profile: true, }, 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', }, }), new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'], }), ].concat( copyPatterns.length > 0 ? [ new CopyWebpackPlugin({ patterns: copyPatterns, }), ] : [] ), watch: true, watchOptions: { aggregateTimeout: 600, ignored: '**/node_modules', followSymlinks: true, }, }; };