webpack.common.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. import FriendlyErrorsWebpackPlugin from '@soda/friendly-errors-webpack-plugin';
  2. import BilldHtmlWebpackPlugin, { logData } from 'billd-html-webpack-plugin';
  3. import CopyWebpackPlugin from 'copy-webpack-plugin';
  4. import ESLintPlugin from 'eslint-webpack-plugin';
  5. import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
  6. import HtmlWebpackPlugin from 'html-webpack-plugin';
  7. import MiniCssExtractPlugin from 'mini-css-extract-plugin';
  8. import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
  9. import ComponentsPlugin from 'unplugin-vue-components/webpack';
  10. import { VueLoaderPlugin } from 'vue-loader';
  11. import { Configuration, DefinePlugin } from 'webpack';
  12. import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
  13. import { merge } from 'webpack-merge';
  14. import WebpackBar from 'webpackbar';
  15. import WindiCSSWebpackPlugin from 'windicss-webpack-plugin';
  16. import {
  17. analyzerEnable,
  18. eslintEnable,
  19. htmlWebpackPluginTitle,
  20. outputDir,
  21. outputStaticUrl,
  22. webpackBarEnable,
  23. windicssEnable,
  24. } from '../constant';
  25. import { chalkINFO, chalkWARN } from '../utils/chalkTip';
  26. import { resolveApp } from '../utils/path';
  27. import devConfig from './webpack.dev';
  28. import prodConfig from './webpack.prod';
  29. console.log(chalkINFO(`读取: ${__filename.slice(__dirname.length + 1)}`));
  30. const sassRules = (isProduction: boolean, module?: boolean) => {
  31. return [
  32. isProduction
  33. ? {
  34. loader: MiniCssExtractPlugin.loader,
  35. options: {
  36. publicPath: outputStaticUrl(isProduction),
  37. },
  38. }
  39. : {
  40. loader: 'vue-style-loader',
  41. options: {
  42. sourceMap: false,
  43. },
  44. },
  45. {
  46. loader: 'css-loader', // 默认会自动找postcss.config.js
  47. options: {
  48. importLoaders: 2, // https://www.npmjs.com/package/css-loader#importloaders
  49. sourceMap: false,
  50. modules: module
  51. ? {
  52. localIdentName: '[name]_[local]_[hash:base64:5]',
  53. }
  54. : undefined,
  55. },
  56. },
  57. {
  58. loader: 'postcss-loader', // 默认会自动找postcss.config.js
  59. options: {
  60. sourceMap: false,
  61. },
  62. },
  63. {
  64. loader: 'sass-loader',
  65. options: {
  66. sourceMap: false,
  67. // 根据sass-loader9.x以后使用additionalData,9.x以前使用prependData
  68. additionalData: `@use 'billd-scss/src/index.scss' as *;@import '@/assets/constant.scss';`,
  69. },
  70. },
  71. ].filter(Boolean);
  72. };
  73. const cssRules = (isProduction: boolean, module?: boolean) => {
  74. return [
  75. isProduction
  76. ? {
  77. loader: MiniCssExtractPlugin.loader,
  78. options: {
  79. publicPath: outputStaticUrl(isProduction),
  80. },
  81. }
  82. : {
  83. loader: 'vue-style-loader',
  84. options: {
  85. sourceMap: false,
  86. },
  87. },
  88. {
  89. loader: 'css-loader', // 默认会自动找postcss.config.js
  90. options: {
  91. importLoaders: 1, // https://www.npmjs.com/package/css-loader#importloaders
  92. sourceMap: false,
  93. modules: module
  94. ? {
  95. localIdentName: '[name]_[local]_[hash:base64:5]',
  96. }
  97. : undefined,
  98. },
  99. },
  100. {
  101. loader: 'postcss-loader', // 默认会自动找postcss.config.js
  102. options: {
  103. sourceMap: false,
  104. },
  105. },
  106. ].filter(Boolean);
  107. };
  108. const commonConfig = (isProduction) => {
  109. const result: Configuration = {
  110. entry: {
  111. main: {
  112. import: './src/main.ts',
  113. },
  114. },
  115. output: {
  116. clean: true, // 在生成文件之前清空 output 目录。替代clean-webpack-plugin
  117. filename: 'js/[name]-[contenthash:6]-bundle.js', // 入口文件打包生成后的文件的文件名
  118. /**
  119. * 入口文件中,符合条件的代码,被抽离出来后生成的文件的文件名
  120. * 如:动态(即异步)导入,默认不管大小,是一定会被单独抽离出来的。
  121. * 如果一个模块既被同步引了,又被异步引入了,不管顺序(即不管是先同步引入再异步引入,还是先异步引入在同步引入),
  122. * 这个模块会打包进bundle.js,而不会单独抽离出来。
  123. */
  124. chunkFilename: 'js/[name]-[contenthash:6]-bundle-chunk.js',
  125. path: resolveApp(`./${outputDir}`),
  126. assetModuleFilename: 'assets/[name]-[contenthash:6].[ext]', // 静态资源生成目录(不管什么资源默认都统一生成到这里,除非单独设置了generator)
  127. /**
  128. * webpack-dev-server 也会默认从 publicPath 为基准,使用它来决定在哪个目录下启用服务,来访问 webpack 输出的文件。
  129. * 所以不管开发模式还是生产模式,output.publicPath都会生效,
  130. * output的publicPath建议(或者绝大部分情况下必须)与devServer的publicPath一致。
  131. * 如果不设置publicPath,它默认就约等于output.publicPath:"",到时候不管开发还是生产模式,最终引入到
  132. * index.html的所有资源都会拼上这个路径,如果不设置output.publicPath,会有问题:
  133. * 比如vue的history模式下,如果不设置output.publicPath,如果路由全都是/foo,/bar,/baz这样的一级路由没有问题,
  134. * 因为引入的资源都是js/bundle.js,css/bundle.css等等,浏览器输入:http://localhost:8080/foo,回车访问,
  135. * 引入的资源就是http://localhost:8080/js/bundle.js,http://localhost:8080/css/bundle.css,这些资源都
  136. * 是在http://localhost:8080/根目录下的没问题,但是如果有这些路由:/logManage/logList,/logManage/logList/editLog,
  137. * 等等超过一级的路由,就会有问题,因为没有设置output.publicPath,所以它默认就是"",此时浏览器输入:
  138. * http://localhost:8080/logManage/logList回车访问,引入的资源就是http://localhost:8080/logManage/logList/js/bundle.js,
  139. * 而很明显,我们的http://localhost:8080/logManage/logList/js目录下没有bundle.js这个资源(至少默认情况下是没有,除非设置了其他属性)
  140. * 找不到这个资源就会报错,这种情况的路由是很常见的,所以建议默认必须手动设置output.publicPath:"/",这样的话,
  141. * 访问http://localhost:8080/logManage/logList,引入的资源就是:http://localhost:8080/js/bundle.js,就不会报错。
  142. * 此外,output.publicPath还可设置cdn地址。
  143. */
  144. publicPath: outputStaticUrl(isProduction),
  145. },
  146. cache: {
  147. type: 'memory',
  148. // type: 'filesystem',
  149. // allowCollectingMemory: true, // 它在生产模式中默认为false,并且在开发模式下默认为true。https://webpack.js.org/configuration/cache/#cacheallowcollectingmemory
  150. // buildDependencies: {
  151. // // 建议cache.buildDependencies.config: [__filename]在您的 webpack 配置中设置以获取最新配置和所有依赖项。
  152. // config: [
  153. // resolveApp('./script/config/webpack.common.ts'),
  154. // resolveApp('./script/config/webpack.dev.ts'),
  155. // resolveApp('./script/config/webpack.prod.ts'),
  156. // resolveApp('.browserslistrc'), // 防止修改了.browserslistrc文件后,但没修改webpack配置文件,webpack不读取最新更新后的.browserslistrc
  157. // resolveApp('babel.config.js'), // 防止修改了babel.config.js文件后,但没修改webpack配置文件,webpack不读取最新更新后的babel.config.js
  158. // ],
  159. // },
  160. },
  161. resolve: {
  162. // 解析路径
  163. extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.mjs'], // 解析扩展名,加上.mjs是因为vant,https://github.com/youzan/vant/issues/10738
  164. alias: {
  165. '@': resolveApp('./src'), // 设置路径别名
  166. script: resolveApp('./script'), // 设置路径别名
  167. vue$: 'vue/dist/vue.runtime.esm-bundler.js', // 设置vue的路径别名
  168. },
  169. fallback: {
  170. /**
  171. * webpack5移除了nodejs的polyfill,更专注于web了?
  172. * 其实webpack5之前的版本能用nodejs的polyfill,也是
  173. * 和nodejs正统的api不一样,比如path模块,nodejs的path,
  174. * __dirname是读取到的系统级的文件绝对路径的(即/user/xxx)
  175. * 但在webpack里面使用__dirname,读取到的是webpack配置的绝对路径/
  176. * 可能有用的polyfill就是crypto这些通用的模块,类似path和fs这些模
  177. * 块其实都是他们的polyfill都是跑在浏览器的,只是有这些api原本的一些功能,
  178. * 还是没有nodejs的能力,所以webpack5干脆就移除了这些polyfill,你可以通过
  179. * 安装他们的polyfill来实现原本webpack4之前的功能,但是即使安装他们的polyfill
  180. * 也只是实现api的功能,没有他们原本在node的能力
  181. */
  182. // path: require.resolve('path-browserify'),
  183. // path: false,
  184. // fs: false,
  185. // child_process: false,
  186. },
  187. },
  188. resolveLoader: {
  189. // 用于解析webpack的loader
  190. modules: ['node_modules'],
  191. },
  192. module: {
  193. noParse: /^(vue|vue-router|naive-ui)$/,
  194. // loader执行顺序:从下往上,从右往左
  195. rules: [
  196. {
  197. test: /\.vue$/,
  198. use: [
  199. {
  200. loader: 'vue-loader',
  201. },
  202. ],
  203. },
  204. {
  205. test: /\.css$/,
  206. exclude: /node_modules/,
  207. oneOf: [
  208. {
  209. resourceQuery: /module/,
  210. use: cssRules(isProduction, true),
  211. },
  212. {
  213. resourceQuery: /\?vue/,
  214. use: cssRules(isProduction),
  215. },
  216. {
  217. test: /\.module\.\w+$/,
  218. use: cssRules(isProduction, true),
  219. },
  220. {
  221. use: cssRules(isProduction),
  222. },
  223. ],
  224. sideEffects: true, // 告诉webpack是有副作用的,不对css进行删除
  225. },
  226. {
  227. test: /\.(sass|scss)$/,
  228. exclude: /node_modules/,
  229. oneOf: [
  230. {
  231. resourceQuery: /module/,
  232. use: sassRules(isProduction, true),
  233. },
  234. {
  235. resourceQuery: /\?vue/,
  236. use: sassRules(isProduction),
  237. },
  238. {
  239. test: /\.module\.\w+$/,
  240. use: sassRules(isProduction, true),
  241. },
  242. {
  243. use: sassRules(isProduction),
  244. },
  245. ],
  246. sideEffects: true,
  247. },
  248. {
  249. test: /\.(jpg|jpeg|png|gif|svg|webp)$/,
  250. type: 'asset',
  251. generator: {
  252. filename: 'img/[name]-[contenthash:6][ext]',
  253. },
  254. parser: {
  255. dataUrlCondition: {
  256. maxSize: 4 * 1024, // 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中
  257. },
  258. },
  259. },
  260. {
  261. test: /\.(eot|ttf|woff2?)$/,
  262. type: 'asset/resource',
  263. generator: {
  264. filename: 'font/[name]-[contenthash:6][ext]',
  265. },
  266. },
  267. ],
  268. },
  269. plugins: [
  270. // 构建进度条
  271. webpackBarEnable && new WebpackBar(),
  272. // 友好的显示错误信息在终端
  273. new FriendlyErrorsWebpackPlugin(),
  274. new ForkTsCheckerWebpackPlugin({
  275. // https://github.com/TypeStrong/fork-ts-checker-webpack-plugin
  276. typescript: {
  277. // extensions: {
  278. // vue: {
  279. // enabled: true,
  280. // compiler: resolveApp('./node_modules/vue/compiler-sfc/index.js'),
  281. // },
  282. // },
  283. diagnosticOptions: {
  284. semantic: true,
  285. syntactic: false,
  286. },
  287. },
  288. /**
  289. * devServer如果设置为false,则不会向 Webpack Dev Server 报告错误。
  290. * 但是控制台还是会打印错误。
  291. */
  292. devServer: false, // 7.x版本:https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/issues/723
  293. // logger: {
  294. // devServer: false, // fork-ts-checker-webpack-plugin6.x版本
  295. // },
  296. /**
  297. * async 为 false,同步的将错误信息反馈给 webpack,如果报错了,webpack 就会编译失败
  298. * async 默认为 true,异步的将错误信息反馈给 webpack,如果报错了,不影响 webpack 的编译
  299. */
  300. async: true,
  301. }),
  302. // 解析vue
  303. new VueLoaderPlugin(),
  304. // eslint-disable-next-line
  305. ComponentsPlugin({
  306. // eslint-disable-next-line
  307. resolvers: [NaiveUiResolver()],
  308. }),
  309. // windicss
  310. windicssEnable && new WindiCSSWebpackPlugin(),
  311. // 该插件将为您生成一个HTML5文件,其中包含使用脚本标签的所有Webpack捆绑包
  312. new HtmlWebpackPlugin({
  313. filename: 'index.html',
  314. title: htmlWebpackPluginTitle,
  315. template: resolveApp('./public/index.html'),
  316. hash: true,
  317. minify: isProduction
  318. ? {
  319. collapseWhitespace: true, // 折叠空白
  320. keepClosingSlash: true, // 在单标签上保留末尾斜杠
  321. removeComments: true, // 移除注释
  322. removeRedundantAttributes: true, // 移除多余的属性(如:input的type默认就是text,如果写了type="text",就移除它,因为不写它默认也是type="text")
  323. removeScriptTypeAttributes: true, // 删除script标签中type="text/javascript"
  324. removeStyleLinkTypeAttributes: true, // 删除style和link标签中type="text/css"
  325. useShortDoctype: true, // 使用html5的<!doctype html>替换掉之前的html老版本声明方式<!doctype>
  326. // 上面的都是production模式下默认值。
  327. removeEmptyAttributes: true, // 移除一些空属性,如空的id,classs,style等等,但不是空的就全删,比如<img alt />中的alt不会删。http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_or_blank_attributes
  328. minifyCSS: true, // 使用clean-css插件删除 CSS 中一些无用的空格、注释等。
  329. minifyJS: true, // 使用Terser插件优化
  330. }
  331. : false,
  332. chunks: ['main'], // 要仅包含某些块,您可以限制正在使用的块
  333. }),
  334. // 注入项目信息
  335. new BilldHtmlWebpackPlugin({
  336. env: 'webpack5',
  337. }),
  338. // 将已存在的单个文件或整个目录复制到构建目录。
  339. new CopyWebpackPlugin({
  340. patterns: [
  341. {
  342. from: 'public', // 复制public目录的文件
  343. // to: 'assets', //复制到output.path下的assets,不写默认就是output.path根目录
  344. globOptions: {
  345. ignore: [
  346. // 复制到output.path时,如果output.paht已经存在重复的文件了,会报错:
  347. // ERROR in Conflict: Multiple assets emit different content to the same filename md.html
  348. '**/index.html', // 忽略from目录下的index.html,它是入口文件
  349. ],
  350. },
  351. },
  352. ],
  353. }),
  354. // new EsbuildPlugin({
  355. // target: 'esnext',
  356. // // define: {
  357. // // DSF_FS: JSON.stringify({ d: 23 }),
  358. // // 'process.env.NODE_ENV': JSON.stringify({ d: 32 }),
  359. // // 'process.env.PUBLIC_PATdH': JSON.stringify({ f: 2 }),
  360. // // // 'process.env.VUE_APP_RELEASE_PROJECT_NAME': JSON.stringify(
  361. // // // process.env.VUE_APP_RELEASE_PROJECT_NAME
  362. // // // ),
  363. // // // 'process.env.VUE_APP_RELEASE_PROJECT_ENV': JSON.stringify(
  364. // // // process.env.VUE_APP_RELEASE_PROJECT_ENV
  365. // // // ),
  366. // // // 'process.env.BilldHtmlWebpackPlugin': JSON.stringify(logData()),
  367. // // // 'process.env': {
  368. // // // BilldHtmlWebpackPlugin: JSON.stringify(logData()),
  369. // // // NODE_ENV: JSON.stringify(
  370. // // // isProduction ? 'production' : 'development'
  371. // // // ),
  372. // // // PUBLIC_PATH: JSON.stringify(outputStaticUrl(isProduction)),
  373. // // // VUE_APP_RELEASE_PROJECT_NAME: JSON.stringify(
  374. // // // process.env.VUE_APP_RELEASE_PROJECT_NAME
  375. // // // ),
  376. // // // VUE_APP_RELEASE_PROJECT_ENV: JSON.stringify(
  377. // // // process.env.VUE_APP_RELEASE_PROJECT_ENV
  378. // // // ),
  379. // // // },
  380. // // },
  381. // }),
  382. // 定义全局变量
  383. new DefinePlugin({
  384. BASE_URL: `${JSON.stringify(outputStaticUrl(isProduction))}`, // public下的index.html里面的favicon.ico的路径
  385. 'process.env': {
  386. BilldHtmlWebpackPlugin: JSON.stringify(logData()),
  387. NODE_ENV: JSON.stringify(isProduction ? 'production' : 'development'),
  388. PUBLIC_PATH: JSON.stringify(outputStaticUrl(isProduction)),
  389. VUE_APP_RELEASE_PROJECT_NAME: JSON.stringify(
  390. process.env.VUE_APP_RELEASE_PROJECT_NAME
  391. ),
  392. VUE_APP_RELEASE_PROJECT_ENV: JSON.stringify(
  393. process.env.VUE_APP_RELEASE_PROJECT_ENV
  394. ),
  395. },
  396. __VUE_OPTIONS_API__: false,
  397. __VUE_PROD_DEVTOOLS__: false,
  398. }),
  399. // ts类型检查
  400. // feat: drop support for Vue.js:https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/pull/801
  401. // https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/tree/v6.5.2#vuejs
  402. // fork-ts-checker-webpack-plugin得配合ts-loader使用。
  403. // new ForkTsCheckerWebpackPlugin({
  404. // // https://github.com/TypeStrong/fork-ts-checker-webpack-plugin
  405. // typescript: {
  406. // extensions: {
  407. // vue: {
  408. // enabled: true,
  409. // compiler: resolveApp('./node_modules/vue/compiler-sfc/index.js'),
  410. // },
  411. // },
  412. // diagnosticOptions: {
  413. // semantic: true,
  414. // syntactic: false,
  415. // },
  416. // },
  417. // /**
  418. // * devServer如果设置为false,则不会向 Webpack Dev Server 报告错误。
  419. // * 但是控制台还是会打印错误。
  420. // */
  421. // // devServer: false, // 7.x版本:https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/issues/723
  422. // logger: {
  423. // devServer: false, // fork-ts-checker-webpack-plugin6.x版本
  424. // },
  425. // /**
  426. // * async 为 false,同步的将错误信息反馈给 webpack,如果报错了,webpack 就会编译失败
  427. // * async 默认为 true,异步的将错误信息反馈给 webpack,如果报错了,不影响 webpack 的编译
  428. // */
  429. // async: true,
  430. // }),
  431. // bundle分析
  432. analyzerEnable &&
  433. new BundleAnalyzerPlugin({
  434. analyzerMode: 'server',
  435. generateStatsFile: true,
  436. statsOptions: { source: false },
  437. }), // configuration.plugins should be one of these object { apply, … } | function
  438. // eslint
  439. eslintEnable &&
  440. new ESLintPlugin({
  441. extensions: ['js', 'jsx', 'ts', 'tsx', 'vue'],
  442. emitError: false, // 发现的错误将始终发出,禁用设置为false.
  443. emitWarning: false, // 找到的警告将始终发出,禁用设置为false.
  444. failOnError: false, // 如果有任何错误,将导致模块构建失败,禁用设置为false
  445. failOnWarning: false, // 如果有任何警告,将导致模块构建失败,禁用设置为false
  446. cache: true,
  447. cacheLocation: resolveApp('./node_modules/.cache/.eslintcache'),
  448. }),
  449. ].filter(Boolean),
  450. };
  451. return result;
  452. };
  453. export default (env) => {
  454. return new Promise((resolve) => {
  455. const isProduction = env.production;
  456. process.env.NODE_ENV = isProduction ? 'production' : 'development';
  457. const configPromise = Promise.resolve(
  458. isProduction ? prodConfig : devConfig
  459. );
  460. configPromise.then(
  461. (config: any) => {
  462. // 根据当前环境,合并配置文件
  463. const mergeConfig = merge(commonConfig(isProduction), config);
  464. console.log(
  465. chalkWARN(
  466. `根据当前环境,合并配置文件,当前是: ${process.env.NODE_ENV!}环境`
  467. )
  468. );
  469. resolve(mergeConfig);
  470. },
  471. (err) => {
  472. console.log(err);
  473. }
  474. );
  475. });
  476. };