前端进阶:一文轻松搞定webpack基础知识进阶与调优

Posted 前端知识营地

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端进阶:一文轻松搞定webpack基础知识进阶与调优相关的知识,希望对你有一定的参考价值。

前端进阶:一文轻松搞定webpack基础知识、进阶与调优

文章目录


  • 文章目录

  • 写在前面

  • Webpack简介

  • 模块打包

  • 资源输入输出

  • 预处理器

  • 代码分片

  • 生产环境配置


写在前面

  • 本文知识来源于作者对《webpack实战 入门、进阶与调优》的知识整理,为获得更好的阅读和观看体验,推荐访问我在wolai的读书笔记。webpack知识笔记
  • 微信公众号用户可通过文末 阅读原文访问

Webpack简介

  • 何为webpack?
    • 模块打包工具:其核心功能是解决模块之间的依赖,把各个模块按照特定的规则和顺序组织在一起,最终合并为一个或多个 js文件。
  • 如果在一个页面中引入多个js文件会有什么缺点?
    • 通过模块的导入导出语句可以清晰地看到模块之间的依赖关系。
    • 模块可以借助打包工具,将多个资源合并后加载。
    • 模块之间作用域是相互隔离的,不会存在命名冲突。
    • 需要手动维护js文件的加载顺序,如果文件之间有隐性的依赖关系,则很容易出现问题。
    • 每个 script标签都会向服务器请求一次静态资源,过多的请求会严重拖慢网页的渲染速度。
    • 每个 script标签中,顶层作用域都是全局作用域,很容易造成 作用域污染和命名冲突。 模块化可以有效的解决上述问题。
  • webpack的优势?
    1. 支持多种模块标准:AMD /  CommonJS / ES6模块。
    2. 有完备的代码分割解决方案,分割打包后的资源,首屏只加载必要的部分,提升首页渲染速度。
    3. 可以处理各种类型的资源:css / 图片 等。
    4. 庞大的社区支持。

模块打包

  • CommonJS
    • 最初为服务端设计,node.js版本。
    • 每个文件即使一个文件,拥有独立的作用域。
    • 导出是一个模块向外暴露自己的唯一方式。CommonJS中通过 module.exports导出模块中的内容。
    • CommonJS中使用 require进行模块的导入。

      如果导入的模块是第一次被加载,这时会首先执行该模块,然后导出执行后的内容。如果模块曾经被加载过,则直接导出第一次加载时执行后的内容。(相当于是一个静态值了)

  • ES6模块
    • 每个文件作为一个模块,每个模块拥有独立的作用域。
    • 通过 exports导出

      命名导出:exports { a, b } 默认导出:exports default a;  (只能导出一个对象)

    • 通过**import **导入,默认导出的变量,导入时可以随意命名,命名导出方式,导入时名称必须一致,可以使用as 重命名。
  • AMDAMD 与CommonJS以及ES6模块的最大区别在于AMD的模块加载方式是 异步的。
    • AMD 中使用define函数来定义模块,使用require来引用模块。
    • 模块可以并行加载,并不会阻塞浏览器。
    • 缺点:语法冗长,回调地狱
  • UMD
    • 并非模块标准,而是一组模块形式的集合,目标是使一个模块能够运行在各种环境。其手段是根据当前的全局对象中的值判断处于那种环境。当前是AMD环境,就以AMD的形式导出,当前是CommonJS就已以CommonJS的形式导出。
    • UMD一般先判断是否AMD环境。
  • CommonJS 与ES6模块的区别CommonJS 对模块依赖的解决是动态的,而ES6模块是静态的。模块导入时:CommonJS是值拷贝,而ES6则是只读的动态映射。

    动态:模块的依赖关系建立在代码运行阶段 静态:模块的依赖关系建立在代码编译阶段

    • CommonJS引入模块时可以动态指定,例如使用if等条件语句引入不同的模块。
  • ES6模块相比CommonJS的优势
    • 死代码检测和排除:通过静态分析工具检测出哪些模块没有被调用过。从而在打包时去掉未使用的模块,以减少资源包的体积。
    • 模块变量类型检查:JS是动态类型语言,不会在代码执行前检查类型错误,ES6模块属于静态类型模块,有助于确保模块之间的传递的值或者接口类型是正确的。
    • 编译器优化:CommonJS无论采用哪种方式,导入的都是一个 对象,而ES6模块直接导入 变量,减少应用层级,程序效率更高。
  • 值拷贝与动态映射
    • 在导入一个模块时,CommonJS导入的是一份导出值的 拷贝,允许对导入的值进行修改。
    • ES6导出的则是值得 动态映射,且该值是 只读的。(一改全改,但只能在模块内部改动)

资源输入输出

  • webpack 资源入口的作用
    • 确定入口模块位置,告诉webpack从哪里开始打包
    • 定义chunk name 。如果只有一个入口,那么默认为“main”,如果有多个入口,则需要为每个入口定义chunk name 作为唯一标识。
  • context 的作用
    • 资源入口的 路径前缀,在配置时必须使用 绝对路径的形式。使得 entry的配置更加简洁。context可以省略,默认为当前项目的 根目录。
  • 如何配置entry
    1. 字符串类型入口: entry:'./src/index.js'
    2. 数组类型入口: entry:['babel-polyfill','./src/index.js']  ( 多个资源预先合并,数组的最后一个元素作为实际的入口路径 )
    3. 对象类型入口:如果要定义 多入口,则必须使用对象的形式.对象属性名是**chunk name **, 对象的属性值是 入口路径
      entry:{
        index:['babel-polyfill','./src/index.js'],
        lib:'./src/lib.js'
      }
    4. 函数类型入口: 返回上述任意一种类型即可. ( 使用函数的优点在于可以设置动态的逻辑来获取工程入口,同时函数支持返回一个 Promise对象来进行异步操作 )
  • 如何配置资源出口:output对象

    path: 资源的输出位置:是打包完成后资源产生的目录.通常为dist目录 publicPath: 资源的请求位置:指定间接资源的请求位置. |路径|说明|示例| |-|-|-| |html相关|以当前页面HTML 所在的路径加上相对路径,构成资源请求的实际url|// 假定当前HTML页面的路径为https://exmple.com/app/index.html
    // 异步加载的资源名为 0.chunk.js

    publicPath:""  // 实际路径为 https://exmple.com/app/0.chunk.js
    publicPath:"./js"  // 实际路径为 https://exmple.com/app/js/0.chunk.js
    publicPath:"../assets/"  // 实际路径为 https://exmple.com/assets/0.chunk.js
    | |Host相关|若当前publicPath以/开始,则表示以当前host name 为基础路径|// 假定当前HTML页面的路径为https://exmple.com/app/index.html
    // 异步加载的资源名为 0.chunk.js

    publicPath:"/"  // 实际路径为 https://exmple.com/0.chunk.js
    publicPath:"/js/"  // 实际路径为 https://exmple.com/js/0.chunk.js
    publicPath:"/assets/"  // 实际路径为 https://exmple.com/assets/0.chunk.js| |CDN相关|使用绝对路径配置publicPath.|// 假定当前HTML页面的路径为https://exmple.com/app/index.html
    // 异步加载的资源名为 0.chunk.js

    publicPath:"http://cdn.com"  // 实际路径为 http://cdn.com/0.chunk.js
    |

    • html 示例
      // 假定当前HTML页面的路径为https://exmple.com/app/index.html
      // 异步加载的资源名为 0.chunk.js

      publicPath:""  // 实际路径为 https://exmple.com/app/0.chunk.js
      publicPath:"./js"  // 实际路径为 https://exmple.com/app/js/0.chunk.js
      publicPath:"../assets/"  // 实际路径为 https://exmple.com/assets/0.chunk.js

    • Host示例
      // 假定当前HTML页面的路径为https://exmple.com/app/index.html
      // 异步加载的资源名为 0.chunk.js

      publicPath:"/"  // 实际路径为 https://exmple.com/0.chunk.js
      publicPath:"/js/"  // 实际路径为 https://exmple.com/js/0.chunk.js
      publicPath:"/assets/"  // 实际路径为 https://exmple.com/assets/0.chunk.js 
    • CDN 相关
      // 假定当前HTML页面的路径为https://exmple.com/app/index.html
      // 异步加载的资源名为 0.chunk.js

      publicPath:"http://cdn.com"  // 实际路径为 http://cdn.com/0.chunk.js

    • 示例:
    1. filename: 控制输出资源的文件名,可以是一个相对路径.

      模板变量:用于动态的为filename 命名. 作用1: 当有多个chunk存在时对不同的chunk进行区分 . 如[name]/ [chunkname] / [id] 对每个chunk来说都是不同的. 作用2: 控制客户端缓存,chunk改变或引起资源的重新加载,从而获取最新内容. ||| |-|-| |变量名称|功能描述| |[hash]|指代webpack此次打包所有资源生成的hash| |[chunkhash]|指代当前chunk内容的hash| |[id]|指代当前chunk的id| |[query]|指代filename配置项中的query| |[name]|指代chunk name ,最常用|

    2. path: 指定资源 输出的位置,要求值必须为 绝对路径,webpack4之后默认为 dist目录
    3. ❗publicPath: 指定资源的 请求位置,注意与path区分.

预处理器

  • loader 概述
    • loader 本质上是一个函数
  • 如何引入loader?
    • modules.rules代表了模块的处理规则.
      // 所有css文件都用css-loader/style-loader处理处理
      module:{
        rules:[{
          text:/\.css$/,
          use:['style-loader','css-loader'//先使用css-loader,然后再用style-loader处理,即从右往左处理  
        }]
      }
  • loader的常用配置
    • exclude: 用来排除指定的内容,可以使用字符串或者正则, 优先级比include高
    • include:用来包含指定的内容, 只能使用正则.
    • issuer: 指定模块的加载者
    • enforce: 指定loader的执行顺序,默认为normal. |类型|说明| |-|-| |pre|在所有loader之前执行| |post|在所有loader之后执行|
  • babel-loader
    • [ ] babel-loader:使Babel与webpack协同工作的模块
    • [ ] @babel/core:Babel编译器的核心代码
    • [ ] @babel/preset-env: Babel官方推荐的预置器,可以根据用户设置的目标环境自动添加所需要的插件和补丁来编译ES6代码。
    • 用途:用来将ES6+代码转换为ES5,使得我们可以在编码中使用最新的特效而不必担心平台兼容性问题。
    • 安装: npm install babel-loader @babel/core @babel/preset-env
    • 注意事项:
    1. 通过exclude 排除对node_modules的编译
    2. 使用缓存,避免重复编译
    3. 禁用模块化转化(不禁用会将ES6模块转化为CommonJS。这将导致Webpack中的tree-shaking特性无法工作)
// babel 的配置示例
rules:{
  test:/\.js$/,             // 匹配所有的js
  exclude:/node_modules/,   // 忽略node_modules
  use:{
     laoder:"babel-loader",// 指定编译器
     options:{
       cacheDirectory:true,// 开启缓存
       presets:[
         'env',
         {modules:fasle}   // 禁用模块转化
       ]
     } 
  }

代码分片

  • 代码分片作用?实现代码的按需加载,提升首屏渲染速度。
    • [ ]  开发过程减少模块的重复打包,提升开发速度。
    • [ ] 减少整体资源的体积。
    • [ ] 分片后的代码可以更好的利用客户端缓存。
  • 通过入口划分代码。将一些通用的库和不常变动的工具放到一个单独的入口中,由于资源不常变化,因此可以有效的利用缓存,减少资源请求。
  • CommonsChunkPluginwebpack 4 之前的版本可用,webpack 4 之后的版本改用SplitChunks
    • 提取Vendor: 将Vue/react等框架代码提取出来。
    • 设置提取范围:通过chunks配置入口模块。
    • 设置提取规则:通过minChunks配置提取规则。
      // 配置案例
      const webpack = require('webpack');
      module.exports = {
        entry:{
          app:'./app.js',
          vendor:['react'],
        },
        output:{
          filename:'[name].js',
        },
        plugins:[
          new webpack.optimize.commonsChunkPlugin({
            name:"vendor",            // 指定公共chunk的名字
            filename:"vendor.js",     // 指定提后后的资源文件名
            chunks:['a','b'],         // 设置提取范围
            minChunks3,             // 只有该模块被引入3次才会被提取为公共模块
          })
        ]


  • SplitChunks参考文章:Webpack之SplitChunks插件用法详解
  • 使用异步加载/按需加载
    • 延时加载暂时用不到的模块。
    • webpack中延时加载的两种方式:import函数(推荐)和require.ensure。
    • import:通过js在页面的head标签中 动态插入一个script标签,从而实现动态加载的效果。
      // webpack 中import函数的使用方法,注意和ES6模块的import语法做区分
      import('./bar.js').then(({add})=>{
        console.log(add(2,3));
      }) 

生产环境配置

  • 如何让webpack根据不同的环境采用不同的配置?
    1. 使用相同的配置文件:在构建开始前将当前所属的环境作为一个变量传递进去,然后再webpack.config.js中通过条件判断语句使用不同的配置
      // package.json
      {
        ...
        "script":{
            "dev":"ENV=development webpack-dev-server",
            "build":"ENV=production webpack"
         }
      }  
      // webpack.config.js
      const ENV = process.env.ENV;
      const isProd  = ENV==='production';

      module.exports = {
        output:{
          filename:isProd?'bundle@[chunkhase].js':'bundle.js'
        },
        mode:ENV

    2. 为不同的环境创建不同的配置文件:将配置新进不同的配置文件中,根据环境加载对应的配置文件。
      // 开发环境配置:webpack.dev.config.js
      // 生产环境配置:webpack.pro.config.js

      // package.json
      {
        ...
        
        // 通过--congig 读取不同的配置文件
        "scripts":{
          "dev":"webpack-dev-server --config=webpack.dev.config.js",
          "build":"webpack --config= webpack.pro.config.js"
        }

  • 如何开启production模式?webpack 4 以上提供了**mode **参数,可以通过它直接切换打包模式。
  • 如何为生产环境和本地环境添加不同的环境变量?webpack中可以使用DefinePlugin进行设置
    // webpack.config.js
    const webpack = require('webpack');
    module.exports={
      entry:'./app.js',
      output:{
        filename:'bundle.js'
      },
      mode:'production',
      plugins:[
        new webpack.DefinePlugin({
          ENV:JSON.stringfy('prodution')
        })
      ]
    }  
    上述代码中,必须使用 JSON.stringfy,因为替换环境变量时是对字符串类型的值进行 完全替换。加入不使用 JSON.stringfy,在替换后就会成为变量名而不是字符串。
  • source-map
    • webpack 如何配置source map
    • 源码映射,帮助调试和定位错误,通常后缀 .map
    • 打开浏览器开发者工具时就会加载对应的源码文件,不打开则不加载
      module.exports = {
        ...
        devtool:"source-map"  // 开启源码视图

  • JS资源压缩
    • JS压缩工具:UglifyJS (webpack 3 已集成) / terser (webpack 4 已集成)
      // webpack 4 配置压缩
      module.exports = {
        entry:"./app.js",
        output:{
          filename:"bundle.js",
        },
        optimization:{
          minimize:true    // 启用压缩
        }


  • CSS资源压缩
    • 压缩css的步骤:压缩css的前提是使用 extract-text-webpack-plugin / mini-css-extract-plugin 提取样式,然后使用 optimize-css-assets-webapck-plugin 进行压缩 。
      const ExtractTextPlugin = require('extract-text-webpack-plugin');
      const OptimizeCssAssetsPlugin = require('optimize-css-assets-webapck-plugin');
      module.exports = {
        module:{
          rules:[
            {
              test:/\.css$/,
              use:ExtractTextPlugin.extract({
                fallback:'style-loader',
                user:'css-loader',
              })
            }
          ]
        },
        plugins:[new OptimizeCssAssetsPlugin({
          assetNameRegExp:/\.optimize\.css$/,   // 生效范围
          cssProcessor:require('cssnano'),      // 压缩处理器,默认为cssnano
          cssProcessorOptions:{                 // 压缩处理器的配置
            discardComments:{                                 
              remove:all 
            }
          },
          canPrint:true                         // 使是否显示log
        })]


参考文章:

  • 附录
  • Webpack之SplitChunks插件用法详解

关注公众号,获取更多前端知识与工具


以上是关于前端进阶:一文轻松搞定webpack基础知识进阶与调优的主要内容,如果未能解决你的问题,请参考以下文章

接口测试很难吗?一文教你用Postman轻松搞定接口测试

web前端进阶知识点 React专精webpack网络协议音视频等

前端基础进阶系列

进阶| Vue 2.x + Webpack 3.x + Nodejs 多页面项目框架(上篇)

一个Vue项目实践,搞定前端进阶

Spark常用算子合集一文搞定spark中的常用转换与行动算子