里程碑二:基于webpack5完成项目工程化基础

160 阅读7分钟

引言

这个阶段是实现将编写的业务文件通过配置webpack打包成HTMLCSSJS等最终产物文件,再通过前期实现的elpis-core服务去加载它们,从而输出对应的页面。

自于抖音“哲玄前端”课程(《大前端全栈实践课》)

一、什么是工程化

前端工程化是指将前端开发过程中的一系列流程和工具进行规范和自动化,从而提高开发效率、减少重复劳动、降低出错率。前端工程化的目标是让前端开发更高效、更优质。也就是开发者一套规范下目录下编写的业务文件,通过解析引擎对其进行分析、转化及优化,编译,打包成符合生产环境标准的产物文件。

工程化流程图.png

工程化工作流程

二、工程化

1、webpack

Webpack 是一个用于 JavaScript 应用程序的静态模块打包工具‌,它通过分析模块间的依赖关系,将各种资源(如 JSCSS、图片等)打包成浏览器可直接运行的静态文件,提升开发效率和性能优化。其中主要分为三大部分:

(1)、解析编译

Webpack可以分析模块之间的依赖关系,根据配置的入口文件entry找出所有依赖的模块,由于webpack本身只能处理JS模块,所以需要通过加载器loader将其他类型文件(如vuecsslesstsx、图片等)转化为有效模块,将其能够打包到产物文件中。

(2)、模块分包

将输出的比较大的JSCSS文件等,按照配置的规则拆分成不同的多个小文件,为不同的入口文件提供不同的资源,提升性能。而其中的

a、第三方包

因为其不会变化(除非更新版本等),可以利用浏览器的缓存机制,让浏览器不会重复加载它们,所以单独打包。

b、公共代码

因其复用性较强,只需要加载一次,不同的都可以使用,所以也单独打包,减少打包的体积。

c、webpack的运行时

与公共代码一样会多次出现且经常变化,故也单独进行打包。

(3)、压缩优化

CSSJS等文件进行压缩优化,其目的是减小最终打包文件的大小,提高加载速度,也变相优化了用户体验。

2、环境分流

Webpack中,可以通过配置不同的Webpack配置文件来区分开发环境和生产环境的配置。

常见做法是创建两个独立的Webpack配置文件,分别针对开发环境和生产环境配置。 开发环境的配置侧重于开发体验和调试工具,而生产环境的配置则更关注代码优化、压缩和资源的优化。

3、Loader

处理那些非 JS 文件。由于 Webpack 自身只理解 JS ,其他类型的文件都需要经过 loader 处理,并将它们转换为有效模块。loader 可以是同步的,也可以是异步的;而且支持链式调用,链中的每个 loader 会处理之前已处理过的资源。常用的有

(1)、Vue-loader

Vue 组件转换为 JS 模块,提取模板、样式和脚本

(2)、Babel-loader

将 ES6+ 代码转换为向后兼容的 JS

(3)、Url-loader

处理图片资源,小文件转为 base64 内联,大文件单独输出。

(4)、Css-loader

解析 CSS 中的 @import 和 url 等引用

(5)、Style-loader

将 CSS 插入到 HTML 的 标签中。

(6)、Less-loader

将 Less 转换为 CSS

(7)、File-loader

将文件复制到输出目录,并返回引用路径。

(8)、Html-loader

用于处理HTML文件,将其中的图片等资源转换为 Webpack 可以识别的模块

(4)、Postcss-loader

用于为 CSS 代码添加浏览器兼容性前缀,以确保在不同浏览器上的一致性

4、Plugin

用来扩展和定制构建过程的工具,可以用于处理和优化资源、自动化任务、注入变量等。插件一般是一个具有 apply 方法的 JS 对象,通过在 Webpack 的配置中配置插件,可以在构建过程中执行额外的操作。常用的有

(1). HtmlWebpackPlugin

用于自动生成HTML文件,并将打包生成的所有资源(如CSS、JS文件)自动注入到生成的HTML文件中。

(2). MiniCssExtractPlugin

用于将CSS代码从打包生成的JS文件中提取出来,创建一个单独的CSS文件,可以实现CSS的异步加载和浏览器缓存优化。

(3). TerserWebpackPlugin

用于对JS代码进行压缩和混淆,减小文件体积,提高加载速度。

(4). OptimizeCSSAssetsWebpackPlugin

用于对提取出的CSS进行压缩和优化。

(5). CleanWebpackPlugin

用于在构建之前清空输出目录,避免旧文件的干扰。

(6). CopyWebpackPlugin

用于将静态文件从源目录复制到输出目录,例如将图片、字体等文件复制到打包后的文件夹中。

(7). DefinePlugin

用于定义全局变量,可以在代码中直接使用这些变量,例如配置环境变量、区分开发环境和生产环境等。

(8). HotModuleReplacementPlugin

用于启用模块热更新,实现在开发过程中无需刷新页面即可看到最新变更的效果。

(9). CompressionWebpackPlugin

用于对打包生成的文件进行gzip压缩,减小文件体积,提升网络传输速度。

(10). ProvidePlugin

用于自动加载模块,当代码中使用到某个模块时,会自动将模块引入,无需手动import。

5、Optimization

配置webpack的优化行为(配置代码分割、模块合并、缓存、Tree Shaking、压缩等优化策略)

  optimization: {
    /**
     * 把js文件打包成3种类型
     * 1、vendor:第三方lib库,基本不会改动,除非依赖版本升级
     * 2、common:业务组件代码的公共部分抽取出来,改动比较少
     * 3、entry.{page}:不同页面的entry里的业务组件代码的差异部分,会经常改动
     * 目的:把改动和引用频率不一样的js区分出来,以达到更好的利用浏览器的缓存效果
     */
    splitChunks: {
      chunks: 'all',//对同步异步模块都进行分割
      maxAsyncRequests: 10,//每次异步加载的最大并行请求数
      maxInitialRequests: 10,//入口点点最大并行请求数
      cacheGroups: {
        vendor: {//第三方依赖库
          name: 'vendor',//模块名称
          test: /[\/]node_modules[\/]/,//打包node_modules下的第三方依赖库
          priority: 20,//优先级,数字越大优先级越高
          enforce: true,//强制打包
          reuseExistingChunk: true,//复用已有的公共chunk
        },
        common: {//公共模块
          test: /[\/]common|widgets[\/]/,
          name: 'common',//模块名称
          minChunks: 2,//最少被引用次数
          minSize: 1,//最小分割文件大小(1 byte)
          priority: 10,//优先级
          reuseExistingChunk: true,//复用已有的公共chunk
        },
      },
    },
    //将webpack运行时生成的代码打包到runtime.js
    runtimeChunk: true,

   //使用TerserPlugin的并发和缓存,提高压缩阶段的性能
    //清除console.log,提高性能
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true,//利用多核cpu的优势来加快压缩速度
        cache: true,//启用缓存加速构建过程
        terserOptions: {
          compress: {
            drop_console: true,//去掉console.log的内容
          }
        }
      })
    ]
  },

6、热更新

热更新.png

热更新原理

允许在应用运行时替换模块。

其工作原理及核心是在开发服务器与浏览器之间建立 WebSocket 连接,在开发服务器中的文件发生变化时,Webpack 重新编译相关模块,通过 WebSocket 将更新后的模块发往浏览器,浏览器接收到更新后替换相应模块。

热更新的优势在于:

  1. 更快的开发反馈:修改代码后立即看到效果
  2. 保持应用状态:不会丢失当前的应用状态
  3. 提高开发效率:减少手动刷新的次数

相关配置如下:

// 开发阶段的entry配置需要加入hmr
Object.keys(baseConfig.entry).forEach(v => {
  // 第三方包不作为hmr入口
  if(v !== "vendor"){
    baseConfig.entry[v] = [
      // 主入口文件
      baseConfig.entry[v],
      // HMR更新入口
      `webpack-hot-middleware/client?path=http://${DEV_SERVER_CONFIG.HOST}:${DEV_SERVER_CONFIG.PORT}/${DEV_SERVER_CONFIG.HMR_PATH}&timeout=${DEV_SERVER_CONFIG.TIMEOUT}&reload=true`
    ]
  }
})

// 开发环境配置
const webpackConfig = merge(baseConfig, {
  mode: "development",
  devtool: 'eval-cheap-module-source-map',
  plugins: [
    new webpack.HotModuleReplacementPlugin({
      multiStep: false,
    }),
  ],
})

在开发服务器上也得做相应的配置,

// 引用devMiddleware中间件(监听文件改动)
app.use(devMiddleware(compiler, {
  writeToDisk: (filePath) => filePath.endsWith(".tpl"),
  publicPath: webpackConfig.output.publicPath,
  headers: {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
    "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization",
  }, 
  stats: {
    colors: true,
  },
}));

// 引用hotMiddleware中间件(实现热更新)
app.use(hotMiddleware(compiler, {
  path: `/${DEV_SERVER_CONFIG.HMR_PATH}`
}));