| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- import FriendlyErrorsWebpackPlugin from '@soda/friendly-errors-webpack-plugin';
- import BilldHtmlWebpackPlugin, { logData } from 'billd-html-webpack-plugin';
- import CopyWebpackPlugin from 'copy-webpack-plugin';
- import ESLintPlugin from 'eslint-webpack-plugin';
- import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
- import HtmlWebpackPlugin from 'html-webpack-plugin';
- import MiniCssExtractPlugin from 'mini-css-extract-plugin';
- import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
- import ComponentsPlugin from 'unplugin-vue-components/webpack';
- import { VueLoaderPlugin } from 'vue-loader';
- // eslint-disable-next-line import/named
- import { Configuration, DefinePlugin } from 'webpack';
- import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
- import { merge } from 'webpack-merge';
- import WebpackBar from 'webpackbar';
- import WindiCSSWebpackPlugin from 'windicss-webpack-plugin';
- import {
- analyzerEnable,
- eslintEnable,
- htmlWebpackPluginTitle,
- outputDir,
- outputStaticUrl,
- webpackBarEnable,
- windicssEnable,
- } from '../constant';
- import { chalkINFO, chalkWARN } from '../utils/chalkTip';
- import { resolveApp } from '../utils/path';
- import devConfig from './webpack.dev';
- import prodConfig from './webpack.prod';
- console.log(chalkINFO(`读取: ${__filename.slice(__dirname.length + 1)}`));
- const sassRules = (isProduction: boolean, module?: boolean) => {
- return [
- isProduction
- ? {
- loader: MiniCssExtractPlugin.loader,
- options: {
- publicPath: outputStaticUrl(isProduction),
- },
- }
- : {
- loader: 'vue-style-loader',
- options: {
- sourceMap: false,
- },
- },
- {
- loader: 'css-loader', // 默认会自动找postcss.config.js
- options: {
- importLoaders: 2, // https://www.npmjs.com/package/css-loader#importloaders
- sourceMap: false,
- modules: module
- ? {
- localIdentName: '[name]_[local]_[hash:base64:5]',
- }
- : undefined,
- },
- },
- {
- loader: 'postcss-loader', // 默认会自动找postcss.config.js
- options: {
- sourceMap: false,
- },
- },
- {
- loader: 'sass-loader',
- options: {
- sourceMap: false,
- // 根据sass-loader9.x以后使用additionalData,9.x以前使用prependData
- additionalData: `@use 'billd-scss/src/index.scss' as *;@import '@/assets/constant.scss';`,
- },
- },
- ].filter(Boolean);
- };
- const cssRules = (isProduction: boolean, module?: boolean) => {
- return [
- isProduction
- ? {
- loader: MiniCssExtractPlugin.loader,
- options: {
- publicPath: outputStaticUrl(isProduction),
- },
- }
- : {
- loader: 'vue-style-loader',
- options: {
- sourceMap: false,
- },
- },
- {
- loader: 'css-loader', // 默认会自动找postcss.config.js
- options: {
- importLoaders: 1, // https://www.npmjs.com/package/css-loader#importloaders
- sourceMap: false,
- modules: module
- ? {
- localIdentName: '[name]_[local]_[hash:base64:5]',
- }
- : undefined,
- },
- },
- {
- loader: 'postcss-loader', // 默认会自动找postcss.config.js
- options: {
- sourceMap: false,
- },
- },
- ].filter(Boolean);
- };
- const commonConfig = (isProduction) => {
- const result: Configuration = {
- entry: {
- main: {
- import: './src/main.ts',
- },
- },
- output: {
- clean: true, // 在生成文件之前清空 output 目录。替代clean-webpack-plugin
- filename: 'js/[name]-[contenthash:6]-bundle.js', // 入口文件打包生成后的文件的文件名
- /**
- * 入口文件中,符合条件的代码,被抽离出来后生成的文件的文件名
- * 如:动态(即异步)导入,默认不管大小,是一定会被单独抽离出来的。
- * 如果一个模块既被同步引了,又被异步引入了,不管顺序(即不管是先同步引入再异步引入,还是先异步引入在同步引入),
- * 这个模块会打包进bundle.js,而不会单独抽离出来。
- */
- chunkFilename: 'js/[name]-[contenthash:6]-bundle-chunk.js',
- path: resolveApp(`./${outputDir}`),
- assetModuleFilename: 'assets/[name]-[contenthash:6].[ext]', // 静态资源生成目录(不管什么资源默认都统一生成到这里,除非单独设置了generator)
- /**
- * webpack-dev-server 也会默认从 publicPath 为基准,使用它来决定在哪个目录下启用服务,来访问 webpack 输出的文件。
- * 所以不管开发模式还是生产模式,output.publicPath都会生效,
- * output的publicPath建议(或者绝大部分情况下必须)与devServer的publicPath一致。
- * 如果不设置publicPath,它默认就约等于output.publicPath:"",到时候不管开发还是生产模式,最终引入到
- * index.html的所有资源都会拼上这个路径,如果不设置output.publicPath,会有问题:
- * 比如vue的history模式下,如果不设置output.publicPath,如果路由全都是/foo,/bar,/baz这样的一级路由没有问题,
- * 因为引入的资源都是js/bundle.js,css/bundle.css等等,浏览器输入:http://localhost:8080/foo,回车访问,
- * 引入的资源就是http://localhost:8080/js/bundle.js,http://localhost:8080/css/bundle.css,这些资源都
- * 是在http://localhost:8080/根目录下的没问题,但是如果有这些路由:/logManage/logList,/logManage/logList/editLog,
- * 等等超过一级的路由,就会有问题,因为没有设置output.publicPath,所以它默认就是"",此时浏览器输入:
- * http://localhost:8080/logManage/logList回车访问,引入的资源就是http://localhost:8080/logManage/logList/js/bundle.js,
- * 而很明显,我们的http://localhost:8080/logManage/logList/js目录下没有bundle.js这个资源(至少默认情况下是没有,除非设置了其他属性)
- * 找不到这个资源就会报错,这种情况的路由是很常见的,所以建议默认必须手动设置output.publicPath:"/",这样的话,
- * 访问http://localhost:8080/logManage/logList,引入的资源就是:http://localhost:8080/js/bundle.js,就不会报错。
- * 此外,output.publicPath还可设置cdn地址。
- */
- publicPath: outputStaticUrl(isProduction),
- },
- cache: {
- type: 'memory',
- // type: 'filesystem',
- // allowCollectingMemory: true, // 它在生产模式中默认为false,并且在开发模式下默认为true。https://webpack.js.org/configuration/cache/#cacheallowcollectingmemory
- // buildDependencies: {
- // // 建议cache.buildDependencies.config: [__filename]在您的 webpack 配置中设置以获取最新配置和所有依赖项。
- // config: [
- // resolveApp('./script/config/webpack.common.ts'),
- // resolveApp('./script/config/webpack.dev.ts'),
- // resolveApp('./script/config/webpack.prod.ts'),
- // resolveApp('.browserslistrc'), // 防止修改了.browserslistrc文件后,但没修改webpack配置文件,webpack不读取最新更新后的.browserslistrc
- // resolveApp('babel.config.js'), // 防止修改了babel.config.js文件后,但没修改webpack配置文件,webpack不读取最新更新后的babel.config.js
- // ],
- // },
- },
- resolve: {
- // 解析路径
- extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.mjs'], // 解析扩展名,加上.mjs是因为vant,https://github.com/youzan/vant/issues/10738
- alias: {
- '@': resolveApp('./src'), // 设置路径别名
- script: resolveApp('./script'), // 设置路径别名
- vue$: 'vue/dist/vue.runtime.esm-bundler.js', // 设置vue的路径别名
- },
- fallback: {
- /**
- * webpack5移除了nodejs的polyfill,更专注于web了?
- * 其实webpack5之前的版本能用nodejs的polyfill,也是
- * 和nodejs正统的api不一样,比如path模块,nodejs的path,
- * __dirname是读取到的系统级的文件绝对路径的(即/user/xxx)
- * 但在webpack里面使用__dirname,读取到的是webpack配置的绝对路径/
- * 可能有用的polyfill就是crypto这些通用的模块,类似path和fs这些模
- * 块其实都是他们的polyfill都是跑在浏览器的,只是有这些api原本的一些功能,
- * 还是没有nodejs的能力,所以webpack5干脆就移除了这些polyfill,你可以通过
- * 安装他们的polyfill来实现原本webpack4之前的功能,但是即使安装他们的polyfill
- * 也只是实现api的功能,没有他们原本在node的能力
- */
- // path: require.resolve('path-browserify'),
- // path: false,
- // fs: false,
- // child_process: false,
- },
- },
- resolveLoader: {
- // 用于解析webpack的loader
- modules: ['node_modules'],
- },
- module: {
- noParse: /^(vue|vue-router|naive-ui)$/,
- // loader执行顺序:从下往上,从右往左
- rules: [
- {
- test: /\.vue$/,
- use: [
- {
- loader: 'vue-loader',
- },
- ],
- },
- {
- test: /\.css$/,
- exclude: /node_modules/,
- oneOf: [
- {
- resourceQuery: /module/,
- use: cssRules(isProduction, true),
- },
- {
- resourceQuery: /\?vue/,
- use: cssRules(isProduction),
- },
- {
- test: /\.module\.\w+$/,
- use: cssRules(isProduction, true),
- },
- {
- use: cssRules(isProduction),
- },
- ],
- sideEffects: true, // 告诉webpack是有副作用的,不对css进行删除
- },
- {
- test: /\.(sass|scss)$/,
- exclude: /node_modules/,
- oneOf: [
- {
- resourceQuery: /module/,
- use: sassRules(isProduction, true),
- },
- {
- resourceQuery: /\?vue/,
- use: sassRules(isProduction),
- },
- {
- test: /\.module\.\w+$/,
- use: sassRules(isProduction, true),
- },
- {
- use: sassRules(isProduction),
- },
- ],
- sideEffects: true,
- },
- {
- test: /\.(jpg|jpeg|png|gif|svg|webp)$/,
- type: 'asset',
- generator: {
- filename: 'img/[name]-[contenthash:6][ext]',
- },
- parser: {
- dataUrlCondition: {
- maxSize: 4 * 1024, // 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中
- },
- },
- },
- {
- test: /\.(eot|ttf|woff2?)$/,
- type: 'asset/resource',
- generator: {
- filename: 'font/[name]-[contenthash:6][ext]',
- },
- },
- ],
- },
- plugins: [
- // 构建进度条
- webpackBarEnable && new WebpackBar(),
- // 友好的显示错误信息在终端
- new FriendlyErrorsWebpackPlugin(),
- new ForkTsCheckerWebpackPlugin({
- // https://github.com/TypeStrong/fork-ts-checker-webpack-plugin
- typescript: {
- // extensions: {
- // vue: {
- // enabled: true,
- // compiler: resolveApp('./node_modules/vue/compiler-sfc/index.js'),
- // },
- // },
- diagnosticOptions: {
- semantic: true,
- syntactic: false,
- },
- },
- /**
- * devServer如果设置为false,则不会向 Webpack Dev Server 报告错误。
- * 但是控制台还是会打印错误。
- */
- devServer: false, // 7.x版本:https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/issues/723
- // logger: {
- // devServer: false, // fork-ts-checker-webpack-plugin6.x版本
- // },
- /**
- * async 为 false,同步的将错误信息反馈给 webpack,如果报错了,webpack 就会编译失败
- * async 默认为 true,异步的将错误信息反馈给 webpack,如果报错了,不影响 webpack 的编译
- */
- async: true,
- }),
- // 解析vue
- new VueLoaderPlugin(),
- // eslint-disable-next-line
- ComponentsPlugin({
- // eslint-disable-next-line
- resolvers: [NaiveUiResolver()],
- }),
- // windicss
- windicssEnable && new WindiCSSWebpackPlugin(),
- // 该插件将为您生成一个HTML5文件,其中包含使用脚本标签的所有Webpack捆绑包
- new HtmlWebpackPlugin({
- filename: 'index.html',
- title: htmlWebpackPluginTitle,
- template: resolveApp('./public/index.html'),
- hash: true,
- minify: isProduction
- ? {
- collapseWhitespace: true, // 折叠空白
- keepClosingSlash: true, // 在单标签上保留末尾斜杠
- removeComments: true, // 移除注释
- removeRedundantAttributes: true, // 移除多余的属性(如:input的type默认就是text,如果写了type="text",就移除它,因为不写它默认也是type="text")
- removeScriptTypeAttributes: true, // 删除script标签中type="text/javascript"
- removeStyleLinkTypeAttributes: true, // 删除style和link标签中type="text/css"
- useShortDoctype: true, // 使用html5的<!doctype html>替换掉之前的html老版本声明方式<!doctype>
- // 上面的都是production模式下默认值。
- removeEmptyAttributes: true, // 移除一些空属性,如空的id,classs,style等等,但不是空的就全删,比如<img alt />中的alt不会删。http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_or_blank_attributes
- minifyCSS: true, // 使用clean-css插件删除 CSS 中一些无用的空格、注释等。
- minifyJS: true, // 使用Terser插件优化
- }
- : false,
- chunks: ['main'], // 要仅包含某些块,您可以限制正在使用的块
- }),
- // 注入项目信息
- new BilldHtmlWebpackPlugin({
- env: 'webpack5',
- }),
- // 将已存在的单个文件或整个目录复制到构建目录。
- new CopyWebpackPlugin({
- patterns: [
- {
- from: 'public', // 复制public目录的文件
- // to: 'assets', //复制到output.path下的assets,不写默认就是output.path根目录
- globOptions: {
- ignore: [
- // 复制到output.path时,如果output.paht已经存在重复的文件了,会报错:
- // ERROR in Conflict: Multiple assets emit different content to the same filename md.html
- '**/index.html', // 忽略from目录下的index.html,它是入口文件
- ],
- },
- },
- ],
- }),
- // new EsbuildPlugin({
- // target: 'esnext',
- // // define: {
- // // DSF_FS: JSON.stringify({ d: 23 }),
- // // 'process.env.NODE_ENV': JSON.stringify({ d: 32 }),
- // // 'process.env.PUBLIC_PATdH': JSON.stringify({ f: 2 }),
- // // // 'process.env.VUE_APP_RELEASE_PROJECT_NAME': JSON.stringify(
- // // // process.env.VUE_APP_RELEASE_PROJECT_NAME
- // // // ),
- // // // 'process.env.VUE_APP_RELEASE_PROJECT_ENV': JSON.stringify(
- // // // process.env.VUE_APP_RELEASE_PROJECT_ENV
- // // // ),
- // // // 'process.env.BilldHtmlWebpackPlugin': JSON.stringify(logData()),
- // // // 'process.env': {
- // // // BilldHtmlWebpackPlugin: JSON.stringify(logData()),
- // // // NODE_ENV: JSON.stringify(
- // // // isProduction ? 'production' : 'development'
- // // // ),
- // // // PUBLIC_PATH: JSON.stringify(outputStaticUrl(isProduction)),
- // // // VUE_APP_RELEASE_PROJECT_NAME: JSON.stringify(
- // // // process.env.VUE_APP_RELEASE_PROJECT_NAME
- // // // ),
- // // // VUE_APP_RELEASE_PROJECT_ENV: JSON.stringify(
- // // // process.env.VUE_APP_RELEASE_PROJECT_ENV
- // // // ),
- // // // },
- // // },
- // }),
- // 定义全局变量
- new DefinePlugin({
- BASE_URL: `${JSON.stringify(outputStaticUrl(isProduction))}`, // public下的index.html里面的favicon.ico的路径
- 'process.env': {
- BilldHtmlWebpackPlugin: JSON.stringify(logData()),
- NODE_ENV: JSON.stringify(isProduction ? 'production' : 'development'),
- PUBLIC_PATH: JSON.stringify(outputStaticUrl(isProduction)),
- VUE_APP_RELEASE_PROJECT_NAME: JSON.stringify(
- process.env.VUE_APP_RELEASE_PROJECT_NAME
- ),
- VUE_APP_RELEASE_PROJECT_ENV: JSON.stringify(
- process.env.VUE_APP_RELEASE_PROJECT_ENV
- ),
- },
- __VUE_OPTIONS_API__: false,
- __VUE_PROD_DEVTOOLS__: false,
- }),
- // ts类型检查
- // feat: drop support for Vue.js:https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/pull/801
- // https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/tree/v6.5.2#vuejs
- // fork-ts-checker-webpack-plugin得配合ts-loader使用。
- // new ForkTsCheckerWebpackPlugin({
- // // https://github.com/TypeStrong/fork-ts-checker-webpack-plugin
- // typescript: {
- // extensions: {
- // vue: {
- // enabled: true,
- // compiler: resolveApp('./node_modules/vue/compiler-sfc/index.js'),
- // },
- // },
- // diagnosticOptions: {
- // semantic: true,
- // syntactic: false,
- // },
- // },
- // /**
- // * devServer如果设置为false,则不会向 Webpack Dev Server 报告错误。
- // * 但是控制台还是会打印错误。
- // */
- // // devServer: false, // 7.x版本:https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/issues/723
- // logger: {
- // devServer: false, // fork-ts-checker-webpack-plugin6.x版本
- // },
- // /**
- // * async 为 false,同步的将错误信息反馈给 webpack,如果报错了,webpack 就会编译失败
- // * async 默认为 true,异步的将错误信息反馈给 webpack,如果报错了,不影响 webpack 的编译
- // */
- // async: true,
- // }),
- // bundle分析
- analyzerEnable &&
- new BundleAnalyzerPlugin({
- analyzerMode: 'server',
- generateStatsFile: true,
- statsOptions: { source: false },
- }), // configuration.plugins should be one of these object { apply, … } | function
- // eslint
- eslintEnable &&
- new ESLintPlugin({
- extensions: ['js', 'jsx', 'ts', 'tsx', 'vue'],
- emitError: false, // 发现的错误将始终发出,禁用设置为false.
- emitWarning: false, // 找到的警告将始终发出,禁用设置为false.
- failOnError: false, // 如果有任何错误,将导致模块构建失败,禁用设置为false
- failOnWarning: false, // 如果有任何警告,将导致模块构建失败,禁用设置为false
- cache: true,
- cacheLocation: resolveApp('./node_modules/.cache/.eslintcache'),
- }),
- ].filter(Boolean),
- };
- return result;
- };
- export default (env) => {
- return new Promise((resolve) => {
- const isProduction = env.production;
- process.env.NODE_ENV = isProduction ? 'production' : 'development';
- const configPromise = Promise.resolve(
- isProduction ? prodConfig : devConfig
- );
- configPromise.then(
- (config: any) => {
- // 根据当前环境,合并配置文件
- const mergeConfig = merge(commonConfig(isProduction), config);
- console.log(
- chalkWARN(
- `根据当前环境,合并配置文件,当前是: ${process.env.NODE_ENV!}环境`
- )
- );
- resolve(mergeConfig);
- },
- (err) => {
- console.log(err);
- }
- );
- });
- };
|