UNPKG

@aniyajs/rotor

Version:

基于webpack5开发的一款专注于打包、运行的工具

608 lines (591 loc) 21.8 kB
delete require.cache[require.resolve("../utils/paths")]; const paths = require("../utils/paths"); const path = require("path"); const resolve = require("resolve"); const fs = require("fs-extra"); const { createEnvironmentHash } = require("../utils/common"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const getCSSModuleLocalIdent = require("../utils/getCSSModuleLocalIdent"); const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin"); const ModuleNotFoundPlugin = require("../utils/ModuleNotFoundPlugin"); const webpack = require("webpack"); const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin"); const { WebpackManifestPlugin } = require("webpack-manifest-plugin"); const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); const ESLintPlugin = require("eslint-webpack-plugin"); const WebpackBar = require("webpackbar"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); // 样式文件匹配规则 const cssReg = /\.css$/; const cssModuleReg = /\.module\.css$/; const lessReg = /\.less$/; const lessModuleReg = /.module\.less$/; /** * @return {import('webpack').Configuration} */ module.exports = (latestCustomConfig, stringified) => { const isEnvDevelopment = process.env.NODE_ENV === "development"; const isEnvProduction = process.env.NODE_ENV === "production"; const devtool = latestCustomConfig?.devtool; const imageSizeLimit = latestCustomConfig?.imageSizeLimit; const fastRefresh = latestCustomConfig?.fastRefresh; const disableESLintPlugin = latestCustomConfig?.disableESLintPlugin; const useTailwindcss = latestCustomConfig?.useTailwindcss; const useTypeScript = fs.existsSync(paths.appTsConfig); // 入口文件 const entry = [ fs.existsSync(paths.appIndexJs) && paths.appIndexJs, fs.existsSync(paths.appTempRouterIndexJs) && paths.appTempRouterIndexJs, useTailwindcss && path.resolve(__filename, "../../assets/css/tailwind.css"), ].filter(Boolean); // style loader 的通用函数 const getStyleLoaders = (cssOptions, preProcessor) => { const loaders = [ isEnvDevelopment && require.resolve("style-loader"), isEnvProduction && { loader: MiniCssExtractPlugin.loader, options: // CSS位于`static/css`,找到`index.html`文件夹。 // 在生产路径中,' publicUrlOrPath '可以是相对路径。 paths.publicUrlOrPath.startsWith(".") ? { publicPath: "../../" } : {}, }, { loader: require.resolve("css-loader"), options: cssOptions, }, { loader: require.resolve("postcss-loader"), options: { postcssOptions: { config: false, plugins: !useTailwindcss ? [ "postcss-flexbugs-fixes", [ "postcss-preset-env", { autoprefixer: { flexbox: "no-2009", }, stage: 3, }, ], "postcss-normalize", ] : [ [ "tailwindcss", { config: path.join(__dirname, "tailwind.config.js"), }, ], "postcss-flexbugs-fixes", [ "postcss-preset-env", { autoprefixer: { flexbox: "no-2009", }, stage: 3, }, ], ], }, sourceMap: isEnvProduction ? devtool : isEnvDevelopment, }, }, ].filter(Boolean); if (preProcessor) { loaders.push( { loader: require.resolve("resolve-url-loader"), options: { sourceMap: isEnvProduction ? devtool : isEnvDevelopment, root: paths.appSrc, }, }, { loader: require.resolve(preProcessor), options: { sourceMap: isEnvProduction ? devtool : isEnvDevelopment, }, }, ); } return loaders; }; return { // 指定环境,对应 package.json 的 browserslist 字段 target: ["browserslist"], // 只显示报错和警告 stats: "errors-warnings", mode: process.env.NODE_ENV, // 如果当前是生产环境,只要出现报错就退出打包过程 bail: isEnvProduction, // 增强调试 devtool, // 应用程序入口 // 当你使用了aniyajs-plugin-tooltik插件时,会存在两个入口文件 // 但是 src/index 文件必须在缓存文件前执行,即优先级最高 entry, output: { // 构建文件夹 path: paths.appBuild, // 在输出中添加' /*filename*/ '注释来生成require() pathinfo: isEnvDevelopment, publicPath: paths.publicUrlOrPath, filename: isEnvProduction ? "static/js/[name].[contenthash:8].js" : isEnvDevelopment && "static/js/bundle.js", chunkFilename: isEnvProduction ? "static/js/[name].[contenthash:8].chunk.js" : isEnvDevelopment && "static/js/[name].chunk.js", assetModuleFilename: "static/media/[name].[hash][ext]", // 将源映射项指向原始磁盘位置(在Windows上格式为URL)。 devtoolModuleFilenameTemplate: isEnvProduction ? (info) => path .relative(paths.appSrc, info.absoluteResourcePath) .replace(/\\/g, "/") : isEnvDevelopment && ((info) => path.resolve(info.absoluteResourcePath).replace(/\\/g, "/")), }, // 缓存生成的webpack模块和块,以提高构建速度。 cache: { type: "filesystem", version: createEnvironmentHash(latestCustomConfig), // 缓存文件位置 cacheDirectory: paths.appWebpackCache, store: "pack", buildDependencies: { defaultWebpack: [path.dirname(require.resolve("webpack"))], config: [__filename], tsconfig: [paths.appTsConfig, paths.appJsConfig].filter((f) => fs.existsSync(f), ), }, }, // 日志选项配置 infrastructureLogging: { level: "none", }, // 忽略文件监听 watchOptions: { ignored: [ paths.appTempPath, paths.appNodeModules, paths.appMockPath, paths.appConfigPath, paths.appBuild, paths.appPackageJson, paths.appJsConfig, ], }, // 优化 optimization: { minimize: isEnvProduction, minimizer: [ new TerserPlugin({ terserOptions: { parse: { // 我们希望更简洁地解析 ecma 8 代码。然而,我们不想要它 // 应用任何可转换为有效 ecma 5 代码的缩小步骤 // 进入无效的 ECMA 5 代码。这就是为什么“压缩”和“输出” // 部分仅应用 ecma 5 安全的转换 // https://github.com/facebook/create-react-app/pull/4234 ecma: 8, }, compress: { ecma: 5, warnings: false, // 由于 Uglify 破坏看似有效的代码的问题而被禁用: // https://github.com/facebook/create-react-app/issues/2376 // 有待进一步调查: // https://github.com/mishoo/UglifyJS2/issues/2011 comparisons: false, // 由于 Terser 破坏有效代码的问题而被禁用: // https://github.com/facebook/create-react-app/issues/5250 // 有待进一步调查: // https://github.com/terser-js/terser/issues/120 inline: 2, }, mangle: { safari10: true, }, // 添加用于在 devtools 中进行分析 // keep_classnames: false, // keep_fnames: false, output: { ecma: 5, comments: false, // 启用是因为表情符号和正则表达式未使用默认值正确缩小 // https://github.com/facebook/create-react-app/issues/2488 ascii_only: true, }, }, }), new CssMinimizerPlugin(), ], }, resolve: { extensions: [".js", ".mjs", ".jsx", ".ts", ".tsx"], alias: { "@": paths.appSrc, config: paths.appConfigPath, }, }, module: { parser: { javascript: { // 指出无效导出名称行为 exportsPresence: "error", }, }, rules: [ // 处理包含sourcemap的node_modules包 devtool && { test: /\.(js|mjs|jsx|ts|tsx|css)$/, exclude: /@babel(?:\/|\\{1,2})runtime/, enforce: "pre", loader: require.resolve("source-map-loader"), }, { // "oneOf"将遍历所有后面的加载器,直到有一个加载器 // 匹配需求当没有加载器匹配时,它将下落 // 回到加载器列表末尾的“file”加载器。 oneOf: [ // TODO:一旦mime-db中有了`image/avif`,就合并这个配置 // https://github.com/jshttp/mime-db { test: [/\.avif$/], type: "asset", mimetype: "image/avif", parser: { dataUrlCondition: { maxSize: imageSizeLimit, }, }, }, // "url"加载器的工作方式类似于"file"加载器,只是它会嵌入资源文件 // 小于指定的字节数,作为数据url,以避免请求。 { test: [/\.jpe?g$/, /\.png$/, /\.bmp$/, /\.webp$/, /\.gif$/], type: "asset", parser: { dataUrlCondition: { maxSize: imageSizeLimit, }, }, }, { test: /\.svg$/, use: [ { loader: require.resolve("@svgr/webpack"), options: { prettier: false, svgo: false, svgoConfig: { plugins: [{ removeViewBox: false }], }, titleProp: true, ref: true, }, }, { loader: require.resolve("file-loader"), options: { name: "static/media/[name].[hash].[ext]", }, }, ], issuer: { and: [/\.(ts|tsx|js|jsx|md|mdx)$/], }, }, // 使用Babel处理应用JS。 // preset包括JSX、Flow、TypeScript和一些ESnext特性。 { test: /\.(ts|tsx|js|jsx|mjs)$/, /* *****待优化***** */ include: [paths.appSrc, paths.appConfigPath], loader: require.resolve("babel-loader"), options: { customize: require.resolve( "babel-preset-react-app/webpack-overrides", ), presets: [ [ require.resolve("babel-preset-react-app"), { runtime: "classic", }, ], ], plugins: [ isEnvDevelopment && fastRefresh && require.resolve("react-refresh/babel"), ].filter(Boolean), // 这是webpack的`babel-loader`的一个功能(不是Babel本身)。 // 它在` src/.aniya/.cache/babel-loader/ `中启用缓存。 // 目录以便更快地重建。 cacheDirectory: paths.appWebpackCacheBabelLoader, // 因为压缩和解压缩需要一定的计算资源, // 启用缓存压缩可能会影响Webpack构建速度 cacheCompression: false, compact: isEnvProduction, }, }, /* *****待优化***** */ // 使用 Bebal 处理应用程序之外的任何 JS // 与应用 JS 不同,只编译标准的 ES 特性 { test: /\.(js|mjs)$/, exclude: /@babel(?:\/|\\{1,2})runtime/, loader: require.resolve("babel-loader"), options: { babelrc: false, configFile: false, compact: false, presets: [ [ require.resolve("babel-preset-react-app/dependencies"), { helpers: true }, ], ], cacheDirectory: paths.appWebpackCacheBabelLoader, // 有关禁用 cacheCompression 的原因的上下文,请参阅 #6846 cacheCompression: false, // 调试到 node_modules 需要 Babel sourcemaps // 代码。如果没有下面的选项,像 VSCode 这样的调试器 // 显示不正确的代码并在错误的行上设置断点。 sourceMaps: !!devtool, inputSourceMap: !!devtool, }, }, // `postcss`加载器自动将前缀应用于我们的css。 // `css`加载器将所有css资源解析为js模块。 // `style`加载器将所有CSS转换为注入<style>标签。 { test: cssReg, exclude: cssModuleReg, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction ? devtool : isEnvDevelopment, modules: { mode: "icss", }, }), sideEffects: true, }, { test: cssModuleReg, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction ? devtool : isEnvDevelopment, modules: { mode: "local", getLocalIdent: getCSSModuleLocalIdent, }, }), }, { test: lessReg, exclude: lessModuleReg, use: getStyleLoaders( { importLoaders: 3, sourceMap: isEnvProduction ? devtool : isEnvDevelopment, modules: { mode: "icss", }, }, "less-loader", ), sideEffects: true, }, { test: lessModuleReg, use: getStyleLoaders( { importLoaders: 3, sourceMap: isEnvProduction ? devtool : isEnvDevelopment, modules: { mode: "local", getLocalIdent: getCSSModuleLocalIdent, }, }, "less-loader", ), }, // “文件”加载器确保这些资产由 WebpackDevServer 提供服务。 // 当你“导入”一个资产时,你会得到它的(虚拟)文件名。 // 在生产中,它们会被复制到 `build` 文件夹中。 // 这个加载器不使用“测试”,所以它会捕获所有模块 // 通过其他装载机掉落。 { // 排除 `js` 文件以保持“css”加载器在注入时正常工作 // 它的运行时,否则将通过“文件”加载器处理。 // 还排除 `ejs` 和 `json` 扩展,以便它们得到处理 // 通过 webpacks 内部加载器。 exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.ejs$/, /\.json$/], type: "asset/resource", }, // ** 停止 **你要添加新的装载机吗? // 确保在“文件”加载器之前添加新的加载器。 ].filter(Boolean), }, ].filter(Boolean), }, plugins: [ // 生成一个带有<script>脚本注入的 `document.ejs`。 // 你能在 `document.ejs` 写 `<%= context.XX.XX %>`,如下: // <link rel="icon" href="<%= context.config.publicPath + 'favicon.ico' %>"> new HtmlWebpackPlugin( Object.assign( {}, { template: paths.appHtml, inject: true, templateParameters: (compilation) => { const context = Object.assign( {}, { config: compilation.options.output, ...latestCustomConfig, }, ); return { context, }; }, }, isEnvProduction ? { minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, }, } : undefined, ), ), // 如果找不到模块,提供一些必要的上下文。 new ModuleNotFoundPlugin(paths.appPath), // 你能在项目中自定义全局变量,数据类型来限制原始类型和函数。 new webpack.DefinePlugin(stringified), // 实验性的React热加载。 isEnvDevelopment && fastRefresh && new ReactRefreshWebpackPlugin({ overlay: false, // ❌设置排除字段有问题。 }), // 如果路径大小写不正确会打印错误 isEnvDevelopment && new CaseSensitivePathsPlugin(), isEnvProduction && new MiniCssExtractPlugin({ // 这些选项类似于webpackOptions.output中的相同选项, // 两个选项都是可选的。 filename: "static/css/[name].[contenthash:8].css", chunkFilename: "static/css/[name].[contenthash:8].chunk.css", }), new WebpackManifestPlugin({ fileName: "asset-manifest.json", publicPath: paths.publicUrlOrPath, generate: (seed, files, entrypoints) => { const manifestFiles = files.reduce((manifest, file) => { manifest[file.name] = file.path; return manifest; }, seed); const entrypointFiles = entrypoints.main.filter( (fileName) => !fileName.endsWith(".map"), ); return { files: manifestFiles, entrypoints: entrypointFiles, }; }, }), /* *****待优化***** */ // // typescript类型检查 // useTypeScript && // new ForkTsCheckerWebpackPlugin({ // async: isEnvDevelopment, // typescript: { // typescriptPath: resolve.sync("typescript", { // basedir: paths.appNodeModules, // }), // configOverwrite: { // compilerOptions: { // sourceMap: isEnvProduction ? devtool : isEnvDevelopment, // skipLibCheck: true, // inlineSourceMap: false, // declarationMap: false, // noEmit: true, // incremental: true, // tsBuildInfoFile: paths.appTsBuildInfoFile, // }, // }, // context: paths.appPath, // diagnosticOptions: { // syntactic: true, // }, // mode: "write-references", // // profile: true // }, // issue: { // include: [ // { file: "../**/src/**/*.{ts,tsx}" }, // { file: "**/src/**/*.{ts,tsx}" }, // ], // exclude: [ // { file: "**/jest/**/*" }, // { file: "**/src/**/?(*.){spec|test}.*" }, // ], // }, // // logger: { // // // Prevent the `fork-ts-checker-webpack-plugin` error message. // // // Because we have custom error messages. // // error: () => {}, // // log: () => {}, // // } // }), // 使用eslint查找和修复代码中的问题 !disableESLintPlugin && fs.existsSync(path.join(paths.appNodeModules, "eslint")) && new ESLintPlugin({ cache: true, cacheLocation: paths.appEslintCacheFile, context: paths.appSrc, eslintPath: require.resolve("eslint", { paths: [paths.appPath] }), extensions: ["ts", "tsx", "js", "jsx", "cjs"], // 自定义消息数据 // formatter failOnError: true, // ESLint options cwd: paths.appPath, baseConfig: { extends: [paths.appEslintPath], }, }), // 实时显示webpack构建过程; new WebpackBar({ profile: true, }), ].filter(Boolean), performance: false, }; };