前端进阶:一文轻松搞定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 重命名。

  • AMD

    AMD 与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对象

    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 ,最常用
  1. path: 指定资源输出的位置,要求值必须为绝对路径,webpack4之后默认为dist目录

  2. ❗publicPath: 指定资源的请求位置,注意与path区分.

> 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
      
      

预处理器

  • 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

    • 用途:用来将ES6+代码转换为ES5,使得我们可以在编码中使用最新的特效而不必担心平台兼容性问题。

    • 安装:npm install babel-loader @babel/core @babel/preset-env

      • babel-loader:使Babel与webpack协同工作的模块

      • @babel/core:Babel编译器的核心代码

      • @babel/preset-env: Babel官方推荐的预置器,可以根据用户设置的目标环境自动添加所需要的插件和补丁来编译ES6代码。

    • 注意事项:

      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}   // 禁用模块转化
       ]
     } 
  }
} 

代码分片

  • 代码分片作用?

    实现代码的按需加载,提升首屏渲染速度。

    • 开发过程减少模块的重复打包,提升开发速度。

    • 减少整体资源的体积。

    • 分片后的代码可以更好的利用客户端缓存。

  • 通过入口划分代码。

    将一些通用的库和不常变动的工具放到一个单独的入口中,由于资源不常变化,因此可以有效的利用缓存,减少资源请求。

  • CommonsChunkPlugin

    webpack 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'],         // 设置提取范围
            minChunks: 3,             // 只有该模块被引入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

    • 源码映射,帮助调试和定位错误,通常后缀.map

    • 打开浏览器开发者工具时就会加载对应的源码文件,不打开则不加载

      • webpack 如何配置source 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
        })]
      } 
      

参考文章:

本文首发于G众号"前端知识营地",点击关注,获取更多优质有趣的内容。


(完)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mingyong.g

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值