从webpack理解工程化

0 阅读10分钟

工程化是什么

工程化的目的是用于将我们书写的前端文件转换成html,css,js文件以及其他的一些资源文件。

所以这些构建工具,它们主要做的事情就是从入口开始 对文件的js,css,字体,图片等各种资源进行编译和压缩转换成浏览器能懂的格式(这一系列可以称为资源处理),在注入html文件中。当文件过多的时候构建工具做这一系列的转换耗时较久,所以又诞生出构建加速和优化来加快时间处理。

所以最后工程化核心就可以理解为两件事情

  • 资源文件处理

  • 加快处理速度和优化


因为常用webpack,所以就以webpack举例一下,先介绍一下资源处理和构建加速,优化有哪些。最后在介绍一下如何配置一个自己的本地化方案。

webpack

资源处理

在webpack中处理资源首先我们需要一个配置来告诉我们资源在哪里。在webpack配置文件中,起这个作用的就是说entry

  • entry 作用是配置入口文件的数量,可以是单文件也可以是多文件

      三种配置方式 string,[ string ],{ key: value }
    

既然有输入那就有输出output

  • output

具体的 配置需要参考 webpack.docschina.org/configurati…

path 打包生成的目录位置

filename 入口 打包生成的名称 可以是具体的名称 也可以是文件指纹

    [name] chunk的名称 就是entry里面的key
    [chunkhash] 一般js文件使用 表示js的内容的hash
    [contenthash] 一般css和图片使用

clean 清除path目录里面的文件

chunkFilename 非入口打包生成的名称 例如 css js

publicPath 用于指定打包后资源(JS、CSS、图片等)的发布路径

crossOriginLoading

library

    类型:string | string[] | object

输入的文件 需要经过一些列的处理,最后才能到输出。这里的处理 就是moduleplugins

  • module 处理不同类型的文件(如 JS、CSS、图片),通过loader转换文件内容。

      常见的loader 知道就行,需要用的时候再去才文档
    
      babel-loader 转换ES6,7新特性
    
      css-loader 解析css文件
    
      style-loader 将css文件插入到head标签中
    
      file-loader 解析图片
    
      url-loader 解析图片
    
      html-loader 解析html文件
    
      thread-loader 多进程打包
    
      less-loader 解析less文件
    
      postcss-loader 解析postcss文件
      
      px2rem-loader 将px转换为rem
    
  • plugins 在 Webpack 构建流程的特定阶段执行自定义任务,如打包优化、资源管理、环境变量注入等。

      commonsChunkPlugin 将chuck相同的模块代码提前公共js
    
      cleanWebpackPlugin 清理构建目录
    
      MiniCssExtractPlugin 将css从bunlde文件提取成独立的css
    
      copyWebpackPlugin 将文件copy到构建输出目录
    
      HtmlWebpackPlugin 创建html文件
    
      terser-webpack-plugin 压缩js
    
      zipWebpackPlugin 将打包出的资源压缩
      
      HardSourceWebpackPlugin 进行缓存
    

加快处理速度和优化

想要加快速度那首先文件需要快速查找

  • resolve 缩小文件搜索范围

      extensions 自动解析确定的扩展
    
      alias 配置别名
    
      modules 告诉 webpack 解析模块时应该搜索的目录
    
      mainFields 配置入口导入
    
      mainFiles 配置入口文件
    
      include 缩小范围
    
      exclude 排除范围
    

并且可以把文件进行压缩

  • 压缩

      html的压缩
      html-webpack-plugin 设置压缩参数
      
      css的压缩
      optimize-css-assets-webpack-plugin
      postcss-loader
          cssnao  css 压缩
    
      js的压缩
      terser-webpack-plugin
    
      
    
      图片的压缩
      image-webpack-loader
      
      compression-webpack-plugin gizp压缩和br压缩
    
      optimization
          minimizer 压缩
    

有了快速查找和压缩 现在需要提速和缓存

  • 提速

      thread-loader 多进程打包
    
      使用HardSourceWebpackPlugin 进行缓存
    
      terser-webpack-plugin
    
      praplle:true 启用多线程
    
      cache:true 启用缓存
    
  • 缓存

      babel-loader 缓存
      
      webpack.DllPlugin 和 webpack.DllReferencePlugin
    
      terser-webpack-plugin 缓存
      
      hard-source-webpack-plugin 缓存 二次构建
      
    

并且在开发的过程中有热更新

  • dev server 开发环境热更新 开发环境提速

      开发服务
      hot:true
      open:true
      port:8080
      contentBase:path.resolve(__dirname,'dist')
      
      webpack-dev-server  是一个服务器 内部是使用 webpack-dev-middleware  和 webpack-hot-middlewar 来完成 整个re更新流程
    
      webpack-dev-middleware 监听文件改动 并将改动的文件编译到内存 
      webpack-hot-middlewar 将改动的文件从内容和页面通讯完成热更新流程
      
      watch webpack的文件监听 全量编译
          watchOptions
          poll 轮询间隔
          aggregateTimeout 防抖时间
          ignored 忽略文件
    

最后还有分包策略

  • 分包策略

       MiniCssExtractPlugin 将css从bunlde文件提取成独立的css
       optimization
           splitChunks 压缩
    
      speed-measure-webpack-plugin 测量构建速度
      
      webpack-bundle-analyzer 分析包的大小
    

由于不同环境在运行时的目的不一样,所以在最后配置的时候有需要分为三个部分

  • 基础配置
  • 生成环境配置
  • 开发环境配置

配置讲解

基础配置

基础配置的目的主要有以下几个内容

入口配置
入口的配置 可以分为单入口和多入口,这里就可以对应前端的单页和多页两种不同的应用。因为多入口配置可以包含单入口配置。所以我这里就直接以多入口构建。

因为入口文件都是放到统一目录下面的并且有统一的格式,所以可以通过glob来获取到所有的文件,动态的创建入口好html的文件注入 这种方式可以有效的解决多页面的快速构建。


const glob = require('glob');
const entry = {};
  const htmlWebpackPlugins = [];
  const entrys = glob.sync(path.join(__dirname, './src/test/*/index.js'));
  entrys.forEach((item)=>{
    const match = item.match(/src\/test\/(.*)\/index\.js/);
    const pageName = match && match[1];
    entry[pageName] = item;
    // html可以进行压缩 和注入配置 不了解的可以参考官方文档
    htmlWebpackPlugins.push(
      new HtmlWebpackPlugin({
        template: path.resolve(__dirname, `./public/${pageName}.html`),
        filename: `${pageName}.html`,
        chunks: [pageName],
        inject: true,
        minify: {
          html5: true,
          collapseWhitespace: true,
          preserveLineBreaks: false,
          minifyCSS: true,
          minifyJS: true,
          removeComments: false
        }
      })
    )
  })

module.exports = {

entry: entry,
plugins:[
    ...,
    ...htmlWebpackPlugins
]

...

}

资源的解析和加速

在解析的时候可以对js,css,文字,图片等解析和补全,并多进程解析和缓存 让第二次速度更快

/**
 *
 * 资源解析 可以分为以下几个部分
 * 解析js
 * 解析ES6
 * 解析Vue
 * 解析CSS
 * 解析less
 * 解析图片
 * 解析字体
 *
 *
 * 样式增强
 * css自动补全
 * px自动转rem
 * 
 *
 * 多进程打包
 * 缓存
 */

module.exports = {
...
module:{
      rules:[
        {
           test: /\.js$/,
           exclude: /node_modules/,
           use: [
             {
               loader: 'thread-loader', 
               options: {
                 workers: 3,
               },
             },
             {
               loader: 'babel-loader',
               options: {
                 cacheDirectory: true,
               },
             }
           ]
        },
        {
           test: /\.vue$/,
           loader: 'vue-loader',
        },
        {
           test: /\.css$/,
           use: ['css-loader',{
             loader: 'postcss-loader',
             options: {
                postcssOptions: {
                   plugins: [
                      require('autoprefixer')({
                         overrideBrowserslist: ['> 1%', 'last 2 versions']
                      }),
                      require('postcss-pxtorem')({
                        rootValue: 15, // 换算的基数
                        unitPrecision: 5, // 允许REM单位增长到的十进制数字
                        propList: ['*'],
                        selectorBlackList: [], // 要忽略并保留为px的选择器
                        ignoreIdentifier: false,  // 忽略单个属性的方法
                        replace: true, // 替换包含REM的规则,而不是添加回退。
                        mediaQuery: false,  // 允许在媒体查询中转换px。
                        minPixelValue: 0 // 设置要替换的最小像素值(3px会被转rem)。默认 0
                      }),
                   ]
                }
             }
          }]
        },
        {
           test: /\.less$/,
           use: ['css-loader','less-loader',{
             loader: 'postcss-loader',
             options: {
                postcssOptions: {
                   plugins: [
                      require('autoprefixer')({
                         overrideBrowserslist: ['> 1%', 'last 2 versions']
                      }),
                      require('postcss-pxtorem')({
                        rootValue: 15, // 换算的基数
                        unitPrecision: 5, // 允许REM单位增长到的十进制数字
                        propList: ['*'],
                        selectorBlackList: [], // 要忽略并保留为px的选择器
                        ignoreIdentifier: false,  // 忽略单个属性的方法
                        replace: true, // 替换包含REM的规则,而不是添加回退。
                        mediaQuery: false,  // 允许在媒体查询中转换px。
                        minPixelValue: 0 // 设置要替换的最小像素值(3px会被转rem)。默认 0
                      }),
                   ]
                }
             }
          }]
        },
        {
           test: /\.(png|jpg|gif|svg)$/,
           use:[
             {
               loader:'file-loader',
               options: {
                 name: '[name]_[hash:8].[ext]'
               }
             },
             {
               loader: 'image-webpack-loader',
               options: {
                 mozjpeg: {
                   progressive: true,
                 },
                 // optipng.enabled: false will disable optipng
                 optipng: {
                   enabled: false,
                 },
                 pngquant: {
                   quality: [0.65, 0.90],
                   speed: 4
                 },
                 gifsicle: {
                   interlaced: false,
                 },
                 // the webp option will enable WEBP
                 webp: {
                   quality: 75
                 }
               }
             }
           ],
        },{
           test: /\.(woff|woff2|eot|ttf|otf)$/,
           loader: 'file-loader',
           options: {
             name: '[name]_[hash:8].[ext]'
           }
        }
      ]
    },
...
常用插件
module.exports = {
...
plugins:[
      new CleanWebpackPlugin(),
      new VueLoaderPlugin(),
      // 把第三方库暴露到window context下
      new webpack.ProvidePlugin({
        Vue: 'vue',
        axios:'axios',
        _: 'lodash'
      }),
      // 定义全局常量
      new webpack.DefinePlugin({
        __VUE_OPTIONS_API__: 'true', // 支持vue解析options api
        __VUE_PROD_DEVTOOLS__: 'false', // 禁用Vue的调试工具
        __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false' //禁用生产环境显示”水合“信息
      }),
    ],
...
}
快速查找
module.exports = {
    ...
    resolve: {
          alias: {
              '@': path.resolve(__dirname, 'src')
          },
          extensions: ['.js', '.vue', '.json']
    },
    ...
}

生产环境

首先我们会引入基础配置,并在基础的配置上新增和开发换不一样的配置

 *  输出
 * 代码压缩
 * 文件指纹
 * tree shaking
 * scope hosting
 * 速度优化
 * 体积优化

首先是输出output 用于指定

  • output用于指定输出文件的存放地址和存放格式
module.export = {
    ...
    output: {
		filename: 'js/[name]_[chunkhash:8].bundle.js',
		path: path.join(process.cwd(),'./app/public/dist/prod'),
		publicPath: '/dist/prod',
		crossOriginLoading: 'anonymous'
	},
    ...
}

在指定 打包的模式为 production 这样 webpack会自动tree shaKing 将js没有使用的删除掉。

  • mode

module.exports = {
    ...
    mode: "production",
    ...
}

  • devtool 大概设置项是下面几个组合 prod 大多直接使用 sourcemap
参数参数解释
eval打包后的模块都使用 eval() 执行,行映射可能不准;不产生独立的 map 文件
cheapmap 映射只显示行不显示列,忽略源自 loader 的 source map
inline映射文件以 base64 格式编码,加在 bundle 文件最后,不产生独立的 map 文件
module增加对 loader source map 和第三方模块的映射
source-map生成独立map文件

module.exports = {
    ...
    mode: "source-map",
    ...
}

  • module

module.exports = {

module: {

rules:[

{

test: /\.css$/,

use: [ miniCssExtractPlugin.loader ]

}

]

},

...

}

  • plugins 生产环境需要把css从文件中提取出来并进行压缩和资源文件的压缩

module.exports = {

...

    plugins:[

        new miniCssExtractPlugin({
          filename: '[name]_[contenthash:8].css'
        }),
        new CompressionWebpackPlugin({
          test: /\.(js|css|html|svg)$/,
          threshold: 10240, // 只有大小大于该值的资源会被处理
          deleteOriginalAssets: false, // 是否删除原文件
          algorithm: 'gzip', // 压缩算法
          minRatio: 0.7 // 只有压缩率小于这个值的资源才会被处理
        }),

        		// 提取css的公共部分,有效利用缓存。非公共部分使用inline
        new MiniCssExtractPlugin({
          chunkFilename: 'css/[name]_[chunkhash:8].bundle.css'
        }),
        // 优化压缩css 资源
        new CssMinimizerPlugin(),

        // 浏览器请求资源时,不发送用户的身份凭证
        new HtmlWebpackInjectAttributesPlugin({
          corssorigin: 'anonymous'
        })

    ],

...

}

  • cache 将文件缓存到本地 让后续构建加快速度

module.exports = {

...

 cache: {
      type: 'filesystem',
      buildDependencies: {
         config: [__filename]
      }
   },

...

}

这里主要就要将 各种优化 说的最多的就是分包的策略。我这里主要把js文件打包成三种类型

  1. vender 第三方lib 基本不会改动,除非依赖版本升级
  2. common 业务组件代码的公共部分抽取出来,改动较少
  3. entry 不同页面entry 里的业务组件代码差异部分,会经常改动

目的,把改动和引用频率不一样的js区分出来,已达到更好利用浏览器缓存的效果

  • optimization:

module.exports = {
    ...
    optimization: {
      minimizer: [
         new CssMinimizerPlugin(),
         new terserWebpackPlugin({
             cache: true, //启用缓存加速构建
            parallel: true, //利用多喝cpu的优势加快压缩速度
            terserOptions: {
               compress: {
                  pure_funcs: ['console.log']
               }
            }
         })
      ],
      splitChunks: {
        chunks: 'all',// 对同步和异步模块都会分隔
        maxAsyncRequests: 10, //每次异步加载的最大并行请求数
        maxInitialRequests: 10,// 入口点的最大并行请求数
        cacheGroups: {
          vendor: {// 第三方依赖库
            test: /[\\/]node_modules[\\/]/, //打包 node_modules中的文件
            name: 'vendor', //模块名称
            priority: 20, // 优先级,数字越大,优先级高
            enforce: true, //强制执行
            reuseExistingChunk: true, // 复用已有的公共chunk
          },
          common: { // 公共模块
            name: 'common', //模块名称
            minChunks: 2, //被两处引用即被归为公共模块
            minSize : 1,// 最小分隔文件大小 1 byte
            priority: 10,// 优先级
            reuseExistingChunk: true, // 复用已有的公共chunk
          }
        }
      }
    },
    ...
}

最后就到了开发环境

开发环境

  • mode 指定development 让webpack 减少开销

module.exports = {

mode: "development",

...

}

  • devtool 同理参照 prod

module.exports = {

devtool: "eval-source-map",

...

}

这个是开发环境的重点,用于在开发的时候将目录的内容存放缓存,然后在请求地址的时候直接从缓存获取内容。并且可以热更新文件。

devServer 其实是起了一个服务器来监控目录文件是否有更新。如果有更新使用webpack-dev-plugin 来将文件缓存到内存里面,并且在浏览器获取某个文件的时候从内存中,将文件取出返回给浏览器。不使用硬盘文件存储。加快构建速度。并且当某个文件更新之后,使用webpack-hot-plugin插件来实现热更新。热更新其实就是网页和devServer启动的服务器之间构建一个WebSocket或者SSE,当webpack-dev-plugin将更新的文件缓存到内存之后,webpack-hot-plugin就回合页面通讯将更新的内容发送到网页 实现热更新功能

  • devServer

module.exports = {

...

devServer: {

    static: './dist',

    port: 8080,

    open: true,

},

...

}

  • plugins 实现热更新需要的插件

module.exports = {

...

plugins:[

    new webpack.HotModuleReplacementPlugin(),

]

...

}


到此,webpack 工程化打包的就已经结束了。其实,对于工程化,我们不需要记住module用什么,plugin用什么,而是要记住 我们用工程化做什么事需要做成的目的。然后根据目的选取使用的工具。这样我们后面不管使用webpack也好,vite和roullup,roulldown等构建工具都能举一反三。

备注引用: 抖音“哲玄前端”《大前端全栈实践》