@aniyajs/rotor
Version:
基于webpack5开发的一款专注于打包、运行的工具
608 lines (591 loc) • 21.8 kB
JavaScript
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,
};
};