|

前端自动化:基于WebPack构建一套前端工程模板

上篇文章使用 webpack 构建了 流程化 工程,实现了前端发部分工作的流程化,这篇博文将继续深入流程化,基于上一篇博文的项目,实现 自动化 的构建。

开始

这篇博文主要整理 webpack 自动化方面的配置,相对于流程化方面会更高级一点,也更接近生产,除基础配置外,主要包括了:

  • CSS浏览器兼容处理;
  • CSS/JS 的优化和压缩;
  • image图片的优化压缩;
  • Base64转码的处理;
  • Js文件的语法降维,即ES6转ES5;
  • 开启Js文件的 SourceMap;
  • webpack配置合并和提取公共配置;
  • 使用webpack-dev-server开启热更新;
  • 增加解析模块扩展名和别名;
  • 增加外部扩展的配置;
  • 使用webpack插件进行打包分析;

CSS 浏览器兼容处理

浏览器兼容主要使用增加浏览器前缀来实现,用到了 postcss-loaderautoprefixer,安装即可。

之后再配置文件中修改样式处理的部分:

// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.(less|css)$/,
        include: [
          path.resolve(__dirname, "src/style/")
        ],
        use: [
          MiniCssExtractPlugin.loader, 
          {
            loader: "css-loader",
            options: { sourceMap: true }
          }, 
          {
            loader: "postcss-loader",
            options: {
              sourceMap: true,
              postcssOptions: {
                plugins: ["autoprefixer"]
              }
            }
          },
          {
            loader: "less-loader",
            options: { sourceMap: true }
          }
        ]
      }
    ]
  },
  ...
}

压缩 CSS/JS

上篇使用loader对CSS文件进行了处理,这里介绍CSS/JS的压缩,主要用来减小体积,实现优化。压缩CSS使用 optimize-css-assets-webpack-plugin,压缩 JS 使用 uglifyjs-webpack-plugin,安装之:

npm i -D optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin

在配置文件中引入插件:

// webpack.config.js
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");

module.exports = {
  ...
  plugins: [
    new OptimizeCssAssetsPlugin({}),
    new UglifyJsPlugin({
      cache: true,    // 启用缓存
      parallel: true, // 并行处理
      sourceMap: true // 开始sourceMap
    })
  ]
}

其中 UglifyJsPlugin 插件在初始化时可以开启 sourceMap 。

图片优化和base64注入

虽然在上篇博文中使用 file-loader 增加了对图片等文件的支持,但是图片并未得到压缩,对页面来说加载负载较大。使用 image-webpack-loader 可以对图片进行优化设置。

修改配置文件的对应部分,这里使用 image-webpack-loader 替换原有的 file-loader

// webpack.config.js
module.exports = {
  ...
  modules: {
    rules: [
      ...
      {
        test: /.(png|jpg|jpeg|svg|gif)$/,
        include: [path.resolve(__dirname, "assets/")],
        use: [
          {
            loader: "image-webpack-loader",
            options: {
              mozjpeg: {
                progressive: true,
                quality: 60
              },
              optpng: { enabled: false },
              pngquant: {
                quality: '60-90',
                speed: 4
              },
              gifsicle: { interlaced: false },
              webp: { quality: 75 }
            }
          }
        ]
      }
    ]
  }
}

上面的配置可以实现图片的压缩,原本比较大的图片可以通过 image-webpack-loader 进行预设压缩,实现快速载入。

不过,对于更小的图片,使用这种压缩后载入的方式依然不够快速。浏览器每一个资源都需要发送一次HTTP请求,前端页面优化的其中一步,就是减少请求的次数,所以对于比较小的图片,可以使用base64转码注入的方式,从一次请求中直接获取到。这里使用 url-loader 进行转码处理。

// webpack.config.js
module.exports = {
  ...
  modules: {
    rules: [
      {
        test: /.(png|jpg|jpeg|svg|gif)$/,
        include: [path.resolve(__dirname, "assets/")],
        use: [
          {
            loader: "url-loader",
            options: { limit: 10000 }
          },
          {
            loader: "image-webpack-loader",
            options: {
              mozjpeg: {
                progressive: true,
                quality: 60
              },
              optpng: { enabled: false },
              pngquant: {
                quality: '60-90',
                speed: 4
              },
              gifsicle: { interlaced: false },
              webp: { quality: 75 }
            }
          }
        ]
      }
    ]
  },
  ...
}

JS文件降维

目前的前端页面开发中,会经常使用到 ES6 的语法,但是浏览器对 ES6 的语法兼容性并不一致,不过对于 ES5 的支持倒是挺不错,所以有必要将 ES6+ 的语法转成 ES5 的语法,这一步可以通过 babel-loader 来实现。

使用 npm i -D babel-loader babel-core bable-preset-env 后,在配置文件中增加配置,主要用于处理JS:

// webpack.config.js
module.exports = {
  ...
  modules: {
    rules: [
      {
        test: /\.js$/,
        include: [path.resolve(__dirname, "src")],
        use: [
          loader: "babel-loader",
          options: {
            cacheDirectory: true,
            presets: ["preset-env"]
          }
        ],
        exclude: /(node_modules)/
      }
    ]
  }
  ...
}

然后在 package.json 下增加 browserslist 字段,用来明示需要兼容的浏览器配置:

// package.json
...
"browserslist": [
  "> 1%",
  "last 2 versions",
  "not ie <= 8"
]
...

重新打包即可生效。

启动热更新

热更新或者一般也叫浏览器自动同步,是指在开发过程中前端某个文件的修改可以及时同步到浏览器页面上,而无需开发者手动刷新进行调试的过程,webpack 提供了 webpack-dev-server 用来实现热更新,使用该功能需要安装 webpack-dev-server 模块。

在配置文件中配置 devServer 属性:

// webpack.config.js
module.exports = {
  ...
  devtool: "inline-source-map",
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    open: true,                 // 自动打开浏览器页面
  }
}

还需要使用 HotModuleReplacementPlugin 插件来开启热替换,该插件是 webpack 自带的,直接在 plugins 里配置即可:

// webpack.config.js
module.exports = {
  ...
  devtool: "inline-source-map",
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    open: true,                 // 自动打开浏览器页面
  },
  plugins: [
    ...
    new webpack.HotModuleReplacementPlugin()
  ]
}

启动调试:

npx webpack-dev-server --config webpack.config.js

之后就可以自动开启一个本地Server,同时监听本地文件是否有修改,如有则热更新到web页面上,这个功能非常使用,一般我们会使用该功能来替代 webpack 的 –watch 功能。

扩展名和别名

webpack 的 resolve 属性可以用来配置模块应该如何被解析。

Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。 Webpack 内置 JavaScript 模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你也可以根据自己的需要修改默认的规则。

alias ,即别名,当我们想 import 一个模块的时候并不希望出现类似 ../../../ 的路径,配置 alias 可以把常用路径作为基础路径并设置别名,例如 vue 里将 @ 作为 src 的目录的别名,我们也可以这么做。

extensions 可以让开发者在导入一个模块时省略后缀名,webpack会根据该项配置尝试补全后缀名后再去导入模块,例如 Vue 中的 .vue 后缀,我们在开发 Vue 组件的时候可以不写 .vue 后缀也能正常导入。

// webpack.config.js
module.exports = {
  ...
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src/")
    },
    extensions: [".js", ".vue"]
  }
  ...
}

外部扩展

如果有一些外部引用的模块不希望打包进项目里,例如 jQuery 或者 lodash 这样的工具库,可以使用 externals 属性来配置。

// webpack.config.js
module.exports = {
  ...
  externals: {
    jquery: "jQuery",
    lodash: "_"
  }
  ...
}

这时如果 main.js 里有相关引入,则不会被webpack打包。这配置主要是方便我们使用相关模块的CDN,提高页面的载入速度。

配置合并与提取

配置文件的合并与提取主要是因为webpack的配置文件会慢慢庞大,由于开发和生产是分离的,庞大的配置系统不便于管理,所以需要将公共部分提取处理,将 dev 和 prod 分离开,开发时使用 dev 版本的配置文件,发布时使用 prod 的配置文件。

这一步需要借助 webpack-merge 模块来实现,需要手动安装一下,然后开始提取公共部分。

公共部分、生成部分和调试部分的配置文件会不相同,也比较好理解,例如开发环境下的CSS无需压缩,JS也无需压缩,是为了方便调试和检查;开发环境中需要使用 webpack-dev-server 热更新,生成环境则不需要等等;按照这个思路,除了生成环境和开发环境的不同点以外的,都应该是公共部分,就包括了图片、字体文件的处理,js的降维,样式文件的抽离等等。

将公共部分命名为 webpack.base.config.js ,开发环境和生产环境下的分别命名为 webpack.dev.config.js 和 webpack.prod.config.js。首先整理 base 部分,即基础公共部分:

// webpack.base.config.js
const path = require("path");
const HtmlPlugin = require("html-webpack-plugin");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");

module.exports = {
  entry: "./src/main.js",
  output: {
    filename: "main.[hash:8].js",
    path: path.resolve(__dirname, "dist")
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src/")
    },
    extensions: [".js"]
  },
  externals: {
    jquery: "jQuery",
    lodash: "_"
  },
  module: {
    rules: [
      {
        test: /.(png|jpg|jpeg|svg|gif)/,
        include: [path.resolve(__dirname, "assets/")],
        use: [
          {
            loader: "url-loader",
            options: { limit: 10000 }
          },
          {
            loader: "image-webpack-loader",
            options: {
              mozjpeg: {
                  progressive: true,
                  quality: 60
              },
              optpng: { enabled: false },
              pngquant: {
                quality: '60-90',
                speed: 4
              },
              gifsicle: { interlaced: false },
              webp: { quality: 75 }
            }
          }
        ]
      },
      {
        test: /\.(woff|woff2|ect|ttf|otf)/,
        include: [
          path.resolve(__dirname, "src/style/fonts")
        ],
        use: ["file-loader"]
      },
      {
        test: /\.js/,
        use: [
          {
            loader: "babel-loader",
            options: {
              cacheDirectory: true,
              presets: ["preset-env"]
            }
          }
        ],
        exclude: /(node_modules)/
      }
    ]
  },
  plugins: [
      new HtmlPlugin({
          title: "Webpack Learn",     // 输出文件的title
          filename: "index.html",     // 输出文件名
      }),
      new CleanWebpackPlugin({
          cleanStaleWebpackAssets: false
      })
  ]
}

公共部分是共用的,需要再dev和prod中导入,先看开发环境下的配置:

// webpack.dev.config.js
const path = require("path");
const webpack = require("webpack");
const { merge } = require("webpack-merge");
const common = require("./webpack.common");

let devConfig = {
  mode: "development",
  output: {
    filename: "main.[hash:8].js",
    path: path.resolve(__dirname, "dist")
  },
  module: {
    rules: [
      {
        test: /\.(less|css)$/,
        include: [
            path.resolve(__dirname, "src/style/")
        ],
        use: [
          "style-loader", 
          {
            loader: "css-loader",
            options: { sourceMap: true }
          }, 
          {
            loader: "postcss-loader",
            options: {
              sourceMap: true,
              postcssOptions: {
                plugins: ["autoprefixer"]
              }
            }
          },
          {
            loader: "less-loader",
            options: { sourceMap: true }
          }
        ]
      }
    ]
  },
  devtool: "inline-source-map",
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    open: true,                 // 自动打开浏览器页面
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]
}

module.exports = merge(common, devConfig);

可以看到在开发环境中需要使用 webpack-dev-server,所以进行了配置,这个在生产环境中是不需要的,再看生产环境的配置,用来将项目打包到生产环境下。

// webpack.prod.config.js
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const {merge} = require("webpack-merge");
const common = require("./webpack.common");

let prodConfig = {
  mode: "production",
  output: {
    filename: "main.[hash:8].js",
    path: path.resolve(__dirname, "dist")
  },
  module: {
    rules: [
      {
        test: /\.(less|css)$/,
        include: [
            path.resolve(__dirname, "src/style/")
        ],
        use: [
          MiniCssExtractPlugin.loader, 
          {
            loader: "css-loader",
            options: { sourceMap: true }
          }, 
          {
            loader: "postcss-loader",
            options: {
              sourceMap: true,
              postcssOptions: {
                plugins: ["autoprefixer"]
              }
            }
          },
          {
            loader: "less-loader",
            options: { sourceMap: true }
          }
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin({
      cleanStaleWebpackAssets: false
    }),
    new MiniCssExtractPlugin({
      filename: "main.[hash:8].css"
    }),
    new OptimizeCssAssetsPlugin({}),
    new UglifyJsPlugin({
      cache: true,
      parallel: true,
      sourceMap: true
    })
  ]
}

module.exports = merge(common, prodConfig);

配置文件提取和分包以后,需要再 package.json 的 script 字段下增加不同的打包命令:

// package.json
...
"script": {
  "watch": "webpack-dev-server --config webpack.dev.config.js",
  "dev": "webpack --config webpack.dev.config.js --mode development",
  "dist": "webpack --config webpack.prod.config.js --mode production"
}
...

打包分析

当运行 npm run dev 完成打包以后,我们可能希望对每个模块的体积进行分析,一遍优化打包。使用 webpack-bundle-analyzer 模块可以帮助实现此功能,这是一个 webpack 插件,安装完成后引入到配置文件里。

// webpack.dev.config.js
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

module.exports = {
  ...
  plugins: [
    ...
    new BundleAnalyzerPlugin()
  ]
  ...
}

这样在完成打包后,webpack 会打开一个浏览器页面,在里面可以想尽的看到每个模块的体积,一遍开发者进行优化或者分离,这是一个转生成环境前的一个很重要的步骤。

总结

本篇博文和上一篇博文 前端流程化的实现:基于WebPack构建一套前端工程模板 主要介绍了基于webpack构建的一套前端工程化模板,基于这个模板可以实现从开发到生产发布的整个过程,通过使用webpack以及webpack生态中的优秀插件大幅提高了前端开发工作,即高效又整洁,很值得推荐。

不过虽然 webpack 目前被广泛使用,但由于webpack配置的复杂性,想要完全精通 webpack 的这一套还是有一定难度,相比而言,与 webpack 类似的工具例如 Gulp 也提供了类似的工具和插件,与 webpack 相比 Gulp 显得更精简和轻量,对于一些小型项目的开发,使用 Gulp 基于 的方式也非常友好,后续有时间我会在整理一些与 Gulp 有关的开发细节和工程化配置细节。

类似文章

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注