资讯专栏INFORMATION COLUMN

通用、封装、简化 webpack 配置

PingCAP / 2828人阅读

摘要:通用封装简化配置现在,基本上前端的项目打包都会用上,因为提供了无与伦比强大的功能和生态。简化配置的一种方式是使用社区封装好的库,比如。封装了的一些基础配置,然后暴露一些额外配置的接口,并附加本地数据模拟功能,详情可以参考主页。

通用、封装、简化 webpack 配置

现在,基本上前端的项目打包都会用上 webpack,因为 webpack 提供了无与伦比强大的功能和生态。但在创建一个项目的时候,总是免不了要配置 webpack,很是麻烦。

简化 webpack 配置的一种方式是使用社区封装好的库,比如 roadhog。roadhog 封装了 webpack 的一些基础配置,然后暴露一些额外配置的接口,并附加本地数据模拟功能(mock),详情可以参考 roadhog 主页。

另一种方式是自己封装 webpack,这样做自己能够更好的掌控项目。

1. 要封装哪些功能

一般搭建一个项目至少需要两种功能:本地开发调试、构建产品代码。

其他的诸如测试、部署到服务器、代码检查、格式优化等功能则不在这篇文章讲解范围,如果有意了解,可以查看我的其他文章。

2. 基础配置 2.1 目录结构(示例,配合后面的代码讲解)

</>复制代码

  1. package.json
  2. dev.js # 本地开发脚本
  3. build.js # 产品构建脚本
  4. analyze.js # 模块大小分析(可选)
  5. # 单页面结构
  6. src/ # 源代码目录
  7. - index.js # js 入口文件
  8. - index.html # html 入口文件
  9. - ... # 其他文件
  10. # 多页面结构
  11. src/ # 源代码目录
  12. - home/ # home 页面工作空间
  13. - index.js # home 页面 js 入口文件
  14. - index.html # home 页面 html 入口文件
  15. - ... # home 页面其他文件
  16. - explore/ # explore 页面工作空间
  17. - index.js # explore 页面 js 入口文件
  18. - index.html # explore 页面 html 入口文件
  19. - ... # explore 页面其他文件
  20. - about/ # about 目录
  21. - company # about/company 页面工作空间
  22. - index.js # about/company 页面 js 入口文件
  23. - index.html # about/company 页面 html 入口文件
  24. - ... # about/company 页面其他文件
  25. - platform # about/platform 页面工作空间
  26. - index.js # about/platform 页面 js 入口文件
  27. - index.html # about/platform 页面 html 入口文件
  28. - ... # about/platform 页面其他文件
  29. - ... # 更多页面
2.2 基础 npm 包

</>复制代码

  1. # package.json
  2. "devDependencies": {
  3. "@babel/core": "^7.1.2", # babel core
  4. "@babel/plugin-syntax-dynamic-import": "^7.0.0", # import() 函数支持
  5. "@babel/plugin-transform-react-jsx": "^7.0.0", # react jsx 支持
  6. "@babel/preset-env": "^7.1.0", # es6+ 转 es5
  7. "@babel/preset-flow": "^7.0.0", # flow 支持
  8. "@babel/preset-react": "^7.0.0", # react 支持
  9. "autoprefixer": "^9.1.5", # css 自动添加厂家前缀 -webkit-, -moz-
  10. "babel-loader": "^8.0.4", # webpack 加载 js 的 loader
  11. "babel-plugin-component": "^1.1.1", # 如果使用 element ui,需要用到这个
  12. "babel-plugin-flow-runtime": "^0.17.0", # flow-runtime 支持
  13. "babel-plugin-import": "^1.9.1", # 如果使用 ant-design,需要用到这个
  14. "browser-sync": "^2.24.7", # 浏览器实例组件,用于本地开发调试
  15. "css-loader": "^1.0.0", # webpack 加载 css 的 loader
  16. "chalk": "^2.4.1", # 让命令行的信息有颜色
  17. "file-loader": "^2.0.0", # webpack 加载静态文件的 loader
  18. "flow-runtime": "^0.17.0", # flow-runtime 包
  19. "html-loader": "^0.5.5", # webpack 加载 html 的 loader
  20. "html-webpack-include-assets-plugin": "^1.0.5", # 给 html 文件添加额外静态文件链接的插件
  21. "html-webpack-plugin": "^3.2.0", # 更方便操作 html 文件的插件
  22. "less": "^3.8.1", # less 转 css
  23. "less-loader": "^4.1.0", # webpack 加载 less 的 loader
  24. "mini-css-extract-plugin": "^0.4.3", # 提取 css 多带带打包
  25. "minimist": "^1.2.0", # process.argv 更便捷处理
  26. "node-sass": "^4.9.3", # scss 转 css
  27. "optimize-css-assets-webpack-plugin": "^5.0.1", # 优化 css 打包,包括压缩
  28. "postcss-loader": "^3.0.0", # 对 css 进行更多操作,比如添加厂家前缀
  29. "sass-loader": "^7.1.0", # webpack 加载 scss 的 loader
  30. "style-loader": "^0.23.0", # webpack 加载 style 的 loader
  31. "uglifyjs-webpack-plugin": "^2.0.1", # 压缩 js 的插件
  32. "url-loader": "^1.1.1", # file-loader 的升级版
  33. "vue-loader": "^15.4.2", # webpack 加载 vue 的 loader
  34. "vue-template-compiler": "^2.5.17", # 配合 vue-loader 使用的
  35. "webpack": "^4.20.2", # webpack 模块
  36. "webpack-bundle-analyzer": "^3.0.2", # 分析当前打包各个模块的大小,决定哪些需要多带带打包
  37. "webpack-dev-middleware": "^3.4.0", # webpack-dev-server 中间件
  38. "webpack-hot-middleware": "^2.24.2" # 热更新中间件
  39. }
2.3 基本命令

</>复制代码

  1. # package.json
  2. "scripts": {
  3. "dev": "node dev.js",
  4. "build": "node build.js",
  5. "analyze": "node analyze.js",
  6. }

</>复制代码

  1. npm run dev # 开发
  2. npm run build # 构建
  3. npm run analyze # 模块分析

如果需要支持多入口构建,在命令后面添加参数:

</>复制代码

  1. npm run dev -- home # 开发 home 页面
  2. npm run analyze -- explore # 模块分析 explore 页面
  3. # 构建多个页面
  4. npm run build -- home explore about/* about/all --env test/prod

home, explore 确定构建的页面;about/*, about/allabout 目录下所有的页面;all, * 整个项目所有的页面

有时候可能还会针对不同的服务器环境(比如测试机、正式机)做出不同的构建,可以在后面加参数

-- 用来分割 npm 本身的参数与脚本参数,参考 npm - run-script 了解详情

2.4 dev.js 配置

开发一般用需要用到下面的组件:

webpack

webpack-dev-server 或 webpack-dev-middleware

webpack-hot-middleware

HotModuleReplacementPlugin

browser-sync

</>复制代码

  1. const minimist = require("minimist");
  2. const webpack = require("webpack");
  3. const HtmlWebpackPlugin = require("html-webpack-plugin");
  4. const devMiddleWare = require("webpack-dev-middleware");
  5. const hotMiddleWare = require("webpack-hot-middleware");
  6. const browserSync = require("browser-sync");
  7. const VueLoaderPlugin = require("vue-loader/lib/plugin");
  8. const { HotModuleReplacementPlugin } = webpack;
  9. const argv = minimist(process.argv.slice(2));
  10. const page = argv._[0];
  11. // 单页面
  12. const entryFile = `${__dirname}/src/index.js`;
  13. // 多页面
  14. const entryFile = `${__dirname}/src/${page}/index.js`;
  15. // 编译器对象
  16. const compiler = webpack({
  17. entry: [
  18. "webpack-hot-middleware/client?reload=true", // 热重载需要
  19. entryFile,
  20. ],
  21. output: {
  22. path: `${__dirname}/dev/`, // 打包到 dev 目录
  23. filename: "index.js",
  24. publicPath: "/dev/",
  25. },
  26. plugins: [
  27. new HotModuleReplacementPlugin(), // 热重载插件
  28. new HtmlWebpackPlugin({ // 处理 html
  29. // 单页面
  30. template: `${__dirname}/src/index.html`,
  31. // 多页面
  32. template: `${__dirname}/src/${page}/index.html`,
  33. }),
  34. new VueLoaderPlugin(), // vue-loader 所需
  35. ],
  36. module: {
  37. rules: [
  38. { // js 文件加载器
  39. loader: "babel-loader",
  40. exclude: /node_modules/,
  41. options: {
  42. presets: ["@babel/preset-env", "@babel/preset-react"],
  43. plugins: [
  44. "@babel/plugin-transform-react-jsx",
  45. "@babel/plugin-syntax-dynamic-import",
  46. ],
  47. },
  48. test: /.(js|jsx)$/,
  49. },
  50. { // css 文件加载器
  51. loader: "style-loader!css-loader",
  52. test: /.css$/,
  53. },
  54. { // less 文件加载器
  55. loader: "style-loader!css-loader!less-loader",
  56. test: /.less$/,
  57. },
  58. { // scss 文件加载器
  59. loader: "style-loader!css-loader!sass-loader",
  60. test: /.(scss|sass)$/,
  61. },
  62. { // 静态文件加载器
  63. loader: "url-loader",
  64. test: /.(gif|jpg|png|woff|woff2|svg|eot|ttf|ico)$/,
  65. options: {
  66. limit: 1,
  67. },
  68. },
  69. { // html 文件加载器
  70. loader: "html-loader",
  71. test: /.html$/,
  72. options: {
  73. attrs: ["img:src", "link:href"],
  74. interpolate: "require",
  75. },
  76. },
  77. { // vue 文件加载器
  78. loader: "vue-loader",
  79. test: /.vue$/,
  80. },
  81. ],
  82. },
  83. resolve: {
  84. alias: {}, // js 配置别名
  85. modules: [`${__dirname}/src`, "node_modules"], // 模块寻址基路径
  86. extensions: [".js", ".jsx", ".vue", ".json"], // 模块寻址扩展名
  87. },
  88. devtool: "eval-source-map", // sourcemap
  89. mode: "development", // 指定 webpack 为开发模式
  90. });
  91. // browser-sync 配置
  92. const browserSyncConfig = {
  93. server: {
  94. baseDir: `${__dirname}/`, // 静态服务器基路径,可以访问项目所有文件
  95. },
  96. startPath: "/dev/index.html", // 开启服务器窗口时的默认地址
  97. };
  98. // 添加中间件
  99. browserSyncConfig.middleware = [
  100. devMiddleWare(compiler, {
  101. stats: "errors-only",
  102. publicPath: "/dev/",
  103. }),
  104. hotMiddleWare(compiler),
  105. ];
  106. browserSync.init(browserSyncConfig); // 初始化浏览器实例,开始调试开发
2.5 build.js 配置

构建过程中,一般会有这些过程:

提取样式文件,多带带打包、压缩、添加浏览器厂家前缀

js 在产品模式下进行打包,并生成 sourcemap 文件

html-webpack-plugin 自动把打包好的样式文件与脚本文件引用到 html 文件中,并压缩

对所有资源进行 hash 化处理(可选)

</>复制代码

  1. const minimist = require("minimist");
  2. const webpack = require("webpack");
  3. const chalk = require("chalk");
  4. const autoprefixer = require("autoprefixer");
  5. const HtmlWebpackPlugin = require("html-webpack-plugin");
  6. const MiniCssExtractPlugin = require("mini-css-extract-plugin");
  7. const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
  8. const VueLoaderPlugin = require("vue-loader/lib/plugin");
  9. const {yellow, red} = chalk;
  10. const argv = minimist(process.argv.slice(2));
  11. const pages = argv._; // ["home", "explore", "about/*", "about/all"]
  12. const allPages = getAllPages(pages); // 根据 page 中的 `*, all` 等关键字,获取所有真正的 pages
  13. // 单页面,只有一个入口,所以只有一个配置文件
  14. const config = { ... };
  15. // 多页面,多个入口,所有有多个配置文件
  16. const configs = allPages.map(page => ({
  17. // 单页面
  18. entry: `${__dirname}/src/index.js`, // js 入口文件
  19. // 多页面
  20. entry: `${__dirname}/src/${page}/index.js`, // js 入口文件
  21. output: {
  22. path: `${__dirname}/dist/`, // 输出路径
  23. filename: "[chunkhash].js", // 输出文件名,这里完全取 hash 值来命名
  24. hashDigestLength: 32, // hash 值长度
  25. publicPath: "/dist/",
  26. },
  27. plugins: [
  28. new MiniCssExtractPlugin({ // 提取所有的样式文件,多带带打包
  29. filename: "[chunkhash].css", // 输出文件名,这里完全取 hash 值来命名
  30. }),
  31. new HtmlWebpackPlugin({
  32. // 单页面
  33. template: `${__dirname}/src/index.html`, // html 入口文件
  34. // 多页面
  35. template: `${__dirname}/src/${page}/index.html`,// html 入口文件
  36. minify: { // 指定如果压缩 html 文件
  37. removeComments: !0,
  38. collapseWhitespace: !0,
  39. collapseBooleanAttributes: !0,
  40. removeEmptyAttributes: !0,
  41. removeScriptTypeAttributes: !0,
  42. removeStyleLinkTypeAttributes: !0,
  43. minifyJS: !0,
  44. minifyCSS: !0,
  45. },
  46. }),
  47. new VueLoaderPlugin(), // vue-loader 所需
  48. new OptimizeCssAssetsPlugin({ // 压缩 css
  49. cssProcessorPluginOptions: {
  50. preset: ["default", { discardComments: { removeAll: true } }],
  51. },
  52. }),
  53. // webpack 打包的 js 文件是默认压缩的,所以这里不需要再额外添加 uglifyjs-webpack-plugin
  54. ],
  55. module: {
  56. rules: [
  57. { // js 文件加载器,与 dev 一致
  58. loader: "babel-loader",
  59. exclude: /node_modules/,
  60. options: {
  61. presets: ["@babel/preset-env", "@babel/preset-react"],
  62. plugins: [
  63. "@babel/plugin-transform-react-jsx",
  64. "@babel/plugin-syntax-dynamic-import",
  65. ],
  66. },
  67. test: /.(js|jsx)$/,
  68. },
  69. { // css 文件加载器,添加了浏览器厂家前缀
  70. use: [
  71. MiniCssExtractPlugin.loader,
  72. "css-loader",
  73. {
  74. loader: "postcss-loader",
  75. options: {
  76. plugins: [
  77. autoprefixer({
  78. browsers: [
  79. "> 1%",
  80. "last 2 versions",
  81. "Android >= 3.2",
  82. "Firefox >= 20",
  83. "iOS 7",
  84. ],
  85. }),
  86. ],
  87. },
  88. },
  89. ],
  90. test: /.css$/,
  91. },
  92. { // less 文件加载器,添加了浏览器厂家前缀
  93. use: [
  94. MiniCssExtractPlugin.loader,
  95. "css-loader",
  96. {
  97. loader: "postcss-loader",
  98. options: {
  99. plugins: [
  100. autoprefixer({
  101. browsers: [
  102. "> 1%",
  103. "last 2 versions",
  104. "Android >= 3.2",
  105. "Firefox >= 20",
  106. "iOS 7",
  107. ],
  108. }),
  109. ],
  110. },
  111. },
  112. "less-loader",
  113. ],
  114. test: /.less$/,
  115. },
  116. { // scss 文件加载器,添加了浏览器厂家前缀
  117. use: [
  118. MiniCssExtractPlugin.loader,
  119. "css-loader",
  120. {
  121. loader: "postcss-loader",
  122. options: {
  123. plugins: [
  124. autoprefixer({
  125. browsers: [
  126. "> 1%",
  127. "last 2 versions",
  128. "Android >= 3.2",
  129. "Firefox >= 20",
  130. "iOS 7",
  131. ],
  132. }),
  133. ],
  134. },
  135. },
  136. "sass-loader",
  137. ],
  138. test: /.(scss|sass)$/,
  139. },
  140. { // 静态文件加载器,与 dev 一致
  141. loader: "url-loader",
  142. test: /.(gif|jpg|png|woff|woff2|svg|eot|ttf|ico)$/,
  143. options: {
  144. limit: 1,
  145. },
  146. },
  147. { // html 文件加载器,与 dev 一致
  148. loader: "html-loader",
  149. test: /.html$/,
  150. options: {
  151. attrs: ["img:src", "link:href"],
  152. interpolate: "require",
  153. },
  154. },
  155. { // vue 文件加载器,与 dev 一致
  156. loader: "vue-loader",
  157. test: /.vue$/,
  158. },
  159. ],
  160. },
  161. resolve: {
  162. alias: {}, // js 配置别名
  163. modules: [`${__dirname}/src`, "node_modules"], // 模块寻址基路径
  164. extensions: [".js", ".jsx", ".vue", ".json"], // 模块寻址扩展名
  165. },
  166. devtool: "source-map", // sourcemap
  167. mode: "production", // 指定 webpack 为产品模式
  168. }));
  169. // 执行一次 webpack 构建
  170. const run = (config, cb) => {
  171. webpack(config, (err, stats) => {
  172. if (err) {
  173. console.error(red(err.stack || err));
  174. if (err.details) {
  175. console.error(red(err.details));
  176. }
  177. process.exit(1);
  178. }
  179. const info = stats.toJson();
  180. if (stats.hasErrors()) {
  181. info.errors.forEach(error => {
  182. console.error(red(error));
  183. });
  184. process.exit(1);
  185. }
  186. if (stats.hasWarnings()) {
  187. info.warnings.forEach(warning => {
  188. console.warn(yellow(warning));
  189. });
  190. }
  191. // 如果是多页面,需要把 index.html => `${page}.html`
  192. // 因为每个页面导出的 html 文件都是 index.html 如果不重新命名,会被覆盖掉
  193. if(cb) cb();
  194. });
  195. };
  196. // 单页面
  197. run(config);
  198. // 多页面
  199. let index = 0;
  200. // go on
  201. const goon = () => {
  202. run(configs[index], () => {
  203. index += 1;
  204. if (index < configs.length) goon();
  205. });
  206. };
  207. goon();
2.6 analyze.js 配置

</>复制代码

  1. const minimist = require("minimist");
  2. const chalk = require("chalk");
  3. const webpack = require("webpack");
  4. const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
  5. const VueLoaderPlugin = require("vue-loader/lib/plugin");
  6. const {yellow, red} = chalk;
  7. const argv = minimist(process.argv.slice(2));
  8. const page = argv._[0];
  9. // 单页面
  10. const entryFile = `${__dirname}/src/index.js`;
  11. // 多页面
  12. const entryFile = `${__dirname}/src/${page}/index.js`;
  13. const config = {
  14. entry: entryFile,
  15. output: {
  16. path: `${__dirname}/analyze/`, // 打包到 analyze 目录
  17. filename: "index.js",
  18. },
  19. plugins: [
  20. new VueLoaderPlugin(), // vue-loader 所需
  21. new BundleAnalyzerPlugin(), // 添加插件
  22. ],
  23. module: {
  24. rules: [
  25. { // js 文件加载器
  26. loader: "babel-loader",
  27. exclude: /node_modules/,
  28. options: {
  29. presets: ["@babel/preset-env", "@babel/preset-react"],
  30. plugins: [
  31. "@babel/plugin-transform-react-jsx",
  32. "@babel/plugin-syntax-dynamic-import",
  33. ],
  34. },
  35. test: /.(js|jsx)$/,
  36. },
  37. { // css 文件加载器
  38. loader: "style-loader!css-loader",
  39. test: /.css$/,
  40. },
  41. { // less 文件加载器
  42. loader: "style-loader!css-loader!less-loader",
  43. test: /.less$/,
  44. },
  45. { // scss 文件加载器
  46. loader: "style-loader!css-loader!sass-loader",
  47. test: /.(scss|sass)$/,
  48. },
  49. { // 静态文件加载器
  50. loader: "url-loader",
  51. test: /.(gif|jpg|png|woff|woff2|svg|eot|ttf|ico)$/,
  52. options: {
  53. limit: 1,
  54. },
  55. },
  56. { // html 文件加载器
  57. loader: "html-loader",
  58. test: /.html$/,
  59. options: {
  60. attrs: ["img:src", "link:href"],
  61. interpolate: "require",
  62. },
  63. },
  64. { // vue 文件加载器
  65. loader: "vue-loader",
  66. test: /.vue$/,
  67. },
  68. ],
  69. },
  70. resolve: {
  71. alias: {}, // js 配置别名
  72. modules: [`${__dirname}/src`, "node_modules"], // 模块寻址基路径
  73. extensions: [".js", ".jsx", ".vue", ".json"], // 模块寻址扩展名
  74. },
  75. mode: "production", // 指定 webpack 为产品模式
  76. };
  77. webpack(config, (err, stats) => {
  78. if (err) {
  79. console.error(red(err.stack || err));
  80. if (err.details) {
  81. console.error(red(err.details));
  82. }
  83. process.exit(1);
  84. }
  85. const info = stats.toJson();
  86. if (stats.hasErrors()) {
  87. info.errors.forEach(error => {
  88. console.error(red(error));
  89. });
  90. process.exit(1);
  91. }
  92. if (stats.hasWarnings()) {
  93. info.warnings.forEach(warning => {
  94. console.warn(yellow(warning));
  95. });
  96. }
  97. });
2.7 扩展配置

你可以根据需要扩展配置,比如添加插件、加载器等,比如:

provide-plugin 可以提供一些全局模块的导出,比如 jquery

define-plugin 可以动态定义一些全局变量

css-loader 可以配置成 css-modules

如果某个页面导出 js bundle 很大,想分割成多个文件,可以使用 dll-plugin、split-chunks-plugin

如果想在命令行显示构建的进度,可以使用 progress-plugin

3. 封装

上面的代码可以封装成一个全局命令,比如 lila,运行上面的命令就可以更简洁:

</>复制代码

  1. lila dev home # 开发 home 页面
  2. lila analyze explore # 模块分析 explore 页面
  3. # 构建多个页面
  4. lila build home explore about/* about/all --env test/prod
后续

更多博客,查看 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99701.html

相关文章

  • 构建Vue-cli通用项目目录

    摘要:使用基于依赖追踪的观察并且使用异步队列更新。为项目配置文件。为项目静态资源目录。其实个人感觉通用项目目录可以很随意的搭配,比如说之后清空目录封装通用组件,像是啊,滑动常用组件。 写在前面的个人体会,大神们可以跳过 这段时间接手一个后台管理项目,从最开始写一点我自己的体会吧。首先Vue,Angular和React是当今主流前端三大框架。Vue是一个用来构建网页的JS库,相比较Angula...

    Winer 评论0 收藏0
  • 全栈开发自学路线

    摘要:前言这里筑梦师是一名正在努力学习的开发工程师目前致力于全栈方向的学习希望可以和大家一起交流技术共同进步用简书记录下自己的学习历程个人学习方法分享本文目录更新说明目录学习方法学习态度全栈开发学习路线很长知识拓展很长在这里收取很多人的建议以后决 前言 这里筑梦师,是一名正在努力学习的iOS开发工程师,目前致力于全栈方向的学习,希望可以和大家一起交流技术,共同进步,用简书记录下自己的学习历程...

    galaxy_robot 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<