Webpack4 学习记录

467 阅读11分钟

Webpack 初识

1. 引例:前端技术的发展历程

  • 第一代:所有的代码写在一个文件中

    • index.html
      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="UTF-8">
        <title>第一代前端开发</title>
        <link rel="stylesheet" href="./index.css">
      </head>
      <body>
        <div id="root"></div>
        <script src="./index.js"></script>
      </body>
      </html>
      
    • index.css
      * {
        padding: 0;
        margin: 0;
      }
      
    • index.js
      var dom = document.getElementById("root");
      
      var header = document.createElement("div");
      header.innerHTML = "This is header";
      dom.append(header);
      
      var content = document.createElement("div");
      content.innerHTML = "This is content";
      dom.append(content);
      
      var footer = document.createElement("div");
      footer.innerHTML = "This is footer";
      dom.append(footer);
      

    缺点:所有代码写在一个文件中,当项目工程变大时,代码文件会变得很大很复杂,难以维护和扩展,复用性很差

  • 第二代:使用面向对象的思维,代码拆分写在不同的文件中

    • index.html
      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="UTF-8">
        <title>第二代前端开发</title>
        <link rel="stylesheet" href="./index.css">
      </head>
      <body>
        <div id="root"></div>
        <script src="index.js"></script>
        <script src="./header.js"></script>
        <script src="./content.js"></script>
        <script src="./footer.js"></script>
      </body>
      </html>
      
    • index.css
      * {
        padding: 0;
        margin: 0;
      }
      
    • header.js
      function Header() {
        var header = document.createElement("div");
        header.innerHTML = "This is header";
        dom.append(header);
      }
      
    • content.js
      function Content() {
        var content = document.createElement("div");
        content.innerHTML = "This is content";
        dom.append(content);
      }
      
    • footer.js
      function Footer() {
        var footer = document.createElement("div");
        footer.innerHTML = "This is footer";
        dom.append(footer);
      }
      
    • index.js
      var dom = document.getElementById("root");
      
      new Header();
      new Content();
      new Footer();
      

    优点:进行模块化划分

    缺点:

    ① 在 index.html 中引入了多个 js 文件,即要发送多个 http 请求去获取这些文件,所以整个页面的加载速度会变慢

    ② 文件之间的依赖关系不直观

    ③ 文件引入顺序不同则可能导致出错

  • 第三代:使用 ES module 模块化开发

    • index.html

      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="UTF-8">
        <title>第三代前端开发</title>
        <link rel="stylesheet" href="./index.css">
      </head>
      <body>
        <div id="root"></div>
        <script src="./dist/main.js"></script>
      </body>
      </html>
      
    • index.css

      * {
        padding: 0;
        margin: 0;
      }
      
    • header.js

      function Header() {
        var dom = document.getElementById("root");
        var header = document.createElement("div");
        header.innerHTML = "This is header";
        dom.append(header);
      }
      
      export default Header;
      
    • content.js

      function Content() {
        var dom = document.getElementById("root");
        var content = document.createElement("div");
        content.innerHTML = "This is content";
        dom.append(content);
      }
      
      export default Content;
      
    • footer.js

      function Footer() {
        var dom = document.getElementById("root");
        var footer = document.createElement("div");
        footer.innerHTML = "This is footer";
        dom.append(footer);
      }
      
      export default Footer;
      
    • index.js

      import Header from "./header.js";
      import Content from "./content,js";
      import Footer from "./footer.js";
      
      new Header();
      new Content();
      new Footer();
      

    好处:模块化开发,解决了依赖不清晰和要求导入顺序的问题

    注意:浏览器并不能直接识别ES6模块系统,所以需要一个工具来将模块和主文件打包在一起,合并成一个文件,该文件浏览器能直接识别解析

2. Webpack的作用与定位

  • Webpack是一个模块打包工具,即Webpack能够识别和解析JS的模块,将多个模块文件打包成一个浏览器能够解析的JS文件,从而解决浏览器不能解析模块化JS代码的问题
  • Webpack不仅仅可以识别ES6-Module,也还可以识别CommonJS,CMD,ADM风格的模块
  • 最开始Webpack只是JavaScript的打包工具,即只能进行CommonJS、CMD、AMD和ES6-Module的打包工作,但随着发展,现在能打包 css,less,sass 以及图片等等许多类型的文件

3. Webpack的安装

  • 创建npm工程项目
    npm init
    
  • 查看npm上webpack的全部版本
    yarn info webpack
    
  • 全局安装
    yarn global add webpack webpack-cli
    
  • 查看全局webpack版本
    webpack -v
    
  • 当前项目工程安装
    yarn add webpack webpack-cli
    
  • 安装特定版本
    yarn add webpack@3.6.0
    
  • 查看当前项目工程webpack版本
    // npx命令是在当前项目工程的node_modules中寻找webpack
    // 有则使用,无则下载后再使用
    npx webpack -v
    
  • 推荐不使用全局安装:因为不同项目所使用的webpack版本可能是不同的
  • webpack-cli的作用:能够在命令行中使用webpack命令和npx webpack命令

4. Webpack配置文件 [mode, entry, output]

  • Webpack配置文件本质是CommonJS的模块,导出的对象描述了Webpack配置信
    const path = require("path");
    
    module.exports = {
        mode: "production(默认)|development",
        entry: {
            main: "./index.js"
        },
        output: {
            filename: "bundle.js",
            // 必须写绝对路径,通过 path 模块完成
            path: "path.resolve(__dirname, 'bundle')" 
        }
    }
    
  • mode: 打包模式
    • 生产模式(production): 默认值,生产模式会压缩打包后文件
    • 开发模式(development): 开发模式不会压缩打包后的文件
  • entry: 入口文件
    • main: 打包后的文件名
    • "./index.js": 进行打包的入口文件
  • output: 输出打包文件
    • filename: 打包入口文件的名字
    • chunkFilename: 打包辅助文件的名字
    • path: 打包文件的存放路径,需要使用绝对路径

5. Webpack进行打包

  • 配好配置文件后,执行npx webpack命令即可进行打包

    • 如果没有自定义的Webpack配置文件,则Webpack会使用默认配置进行打包
    • Webpack能自动识别的配置文件的名字为webpack.config.js
    • 如果配置文件不叫webpack.config.js,则需要加上 --config 配置文件路径
      npx webpack --config my.js
      
    • env环境变量:在执行webpack打包命令时,可以通过设置环境变量env,将特定的参数传递到webpack.config.js文件中使用
    webpack --env.mod=production  webpack.config.js
    
    // webpack.config.js
    console.log(env.production); // production
    
  • 配置npm脚本

    // package.json
    {
      "script": {
        "bundle": "npx webpack --config webpack.config.js"
      }
    }
    
    • 可以使用npm run bundle使用执行webpack命令
  • 打包完成后的提示信息

    信息 说明
    Hash 哈希值,本次打包的唯一标识
    Version webpack的版本
    Time 打包耗时
    Asset 打包出来的文件
    Size 打包出来的文件的大小
    Chunks 打包出来的文件的序号
    Chunks 打包出来的文件的别称
  • 打包多个文件,并指定使用前缀

    module.exports = {
        entry: {
            main: "./src/index.js",
            sub: "./src/index.js"
        },
        output: {
            publicPath: 'http://cdn.com.cn'
            filename: [name].js
        }
    }
    
    • entry对象中可以放多个属性,每个属性名为打包文件名字,属性值为源文件路径
    • output中可以指定使用前缀,即在入口文件引入打包文件时,需要加上该前缀

6. Chunk

  • 打包后生成的每个js文件称为一个Chunk

7. 打包过程分析

  • 在打包时,可以导出打包过程描述文件
    webpack --config my.config.js > state.json
    
  • 可以将描述文件上传到Webpack官网提供的分析工具,进行分析
    http://webpack.github.com/analyse
    

Loader [module]

1. Loader是什么

  • Loader是一种打包方案,它告诉Webpack针对某种类型的文件,如何进行打包

2. 为什么需要Loader

  • Webpack默认情况下,只能打包JavaScript的模块文件,即Webpack只能打包后缀名为.js的文件
  • 为了让Webpack能够打包其他类型的文件,比如打包图片,我们需要定义其他文件的打包方案(即Loader),并且告诉Webpack如何使用(即写配置文件)

3. 模块打包配置

  • 例子
    const path = require("path");
    
    module.exports = {
        entry: {
            main: "./index.js"
        },
        output: {
            filename: "bundle.js",
            path: "path.resolve(__dirname, 'bundle')" 
            // 必须写绝对路径,通过 path 模块完成
        },
        mode: "production(默认)|development",
        module: {
            rules: [
                {
                    test: /\.jpg$/,
                    use: [
                        {
                            loader: "file-loader"
                        }
                    ]
                }, {
                    test: /\.vue$/,
                    use: [
                        {
                            loader: "vue-loader"
                        }
                    ]
                }
            ]
        }
    }
    
  • module: 模块打包配置
    • rules: 打包规则,值是一个数组,数组内存对象,每个对象描述一个规则
      • test: 模块名称匹配的特征(使用正则表达式)
      • use: 使用的打包方案,值是一个数组,数组内存方案描述对象

4. file-loader

  • 功能:告诉Webpack如何打包静态文件,包括打包字体文件

  • 安装file-loader

    yarn add file-loader
    
  • 基础使用

    module.exports =  {
        rules: [
            {
                test: /\.(jpg|png|gif)$/,
                use: [ 
                    {
                        loader: "file-loader"
                    }
                ]
            }
        ]
    }
    
  • 选项

    module.exports =  {
        rules: [
            {
                test: /\.jpg$/,
                use: [
                    {
                        loader: "file-loader",
                        options: {
                            name: '[name]_[hash].[ext]',
                            outputPath: 'images/'
                        }
                    }
                ]
            }
        ]
    }
    
    • options: 使用选项
      • name: 打包后静态文件的名字
      • outputPath: 打包后静态文件存放的路径
  • 占位符

    • [name]: 原始文件的名称
    • [hash]: 原始文件的哈希值
    • [ext]: 原始文件的后缀名
    • [contenthash]: 打包文件内容的哈希值,一般用于阻止浏览器缓存

5. url-loader

  • 作用

    • 能够实现file-loader的全部功能
    • 增强功能是url-loader会将图片转为base64,所以只适合打包小图片文件
  • 选项

    module.exports =  {
        rules: [
            {
                test: /\.jpg$/,
                use: [
                    {
                        loader: "file-loader",
                        options: {
                            name: '[name]_[hash].[ext]',
                            outputPath: 'images/',
                            limit: 2000
                        }
                    }
                ]
            }
        ]
    }
    
    • limit: 大小限制,只有文件大小小于2kb时才会使用该Loader打包
    • name: 打包后静态文件的名字
    • outputPath: 打包后静态文件存放的路径

6. css-loader和style-loader

  • 功能
    • css-loader功能:解析css的依赖关系,合并成一个css文件
    • style-loader功能:将合并后的css文件挂载到header标签中
    • 两个的功能合起来:打包css类型的文件
  • 例子
    module.exports =  {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            importLoaders: 2,
                            modules: true
                        }
                    }
                ]
            }
        ]
    }
    
    • 使用Loader时:会从后往前使用数组中描述的Loader,即先使用css-loader,再使用style-loader
    • importLoaders: 2: 表示执行该loader前还要执行后面的两个loader
    • modules: true: 开启css的模块化打包,避免全局css

7. sass-loader

  • 安装
    yarn add sass-loader node-sass
    
  • 使用
    module.exports =  {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        "style-loader"
                    }, {
                        "css-loader"
                    },{
                        "scss-loader"
                    }
                ]
            }
        ]
    }
    
    • 使用Loader时:会从后往前使用数组中描述的Loader,即先使用sass-loader,再使用css-loader,再使用style-loader

Plugin [plguins]

1. Plugin是什么

  • plugin是插件,它可以在webpack运行的某一个时刻,帮你自动做一些事情

2. html-webpack-plugin

  • 功能:在打包结束后会自动生成一个html文件(入口文件),并把打包后的js文件自动引入到这个html文件中
  • 安装
    yarn add html-webpack-plugin
    
  • 使用
    // 导入html-webpack-plugin插件
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const path = require("path");
    
    module.exports = {
        mode: development,
        entry: {
            main: index.js
        },
        output: {
            name: "bundle.js",
            path: path.resolve(__dirname, "bundle")
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: "./template.html"
            })
        ]
    }
    
    • plugins: 配置插件,是一个数组,里面存插件对象
    • 新建HtmlWebpackPlugin时,可以指定使用自定义模板,则生成入口html文件时,会以该模板作为基础生成

3. clean-webpack-plugin

  • 功能:打包时,会先清空生成目录中的文件
  • 安装
    yarn add clean-webpack-plugin
    
  • 使用
    // 导入clean-webpack-plugin插件
    const CleanWebpackPlugin = require("clean-webpack-plugins")
    // 导入html-webpack-plugin插件
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const path = require("path");
    
    module.exports = {
        mode: development,
        entry: {
            main: index.js
        },
        output: {
            name: "bundle.js",
            path: path.resolve(__dirname, "bundle")
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: "./template.html"
            }),
            new CleanWebpackPlugin("生成目录")
        ]
    }
    

4. ProviderPlugin

  • 功能:webpackn能够识别特定的字符串,然后自动进行模块导入,而不用自己写import...from...语句,这称为垫片(shimming)
  • 使用
    const webpack = require("webpack");
    
    module.exports = {
        plugins: [
            new webpack.ProviderPlugin({
                $: "jquery",
                _join: ['lodash', 'join']
            })
        ]
    }
    
    • 当代码中出现 $ 这个字符串时,webpack会自动导入jQuery,而不用自己写
      import $ from "jquery";
      
    • 当代码中出现"_join"这个字符串时,webpack会自动导入lodash,将"_join"替换为lodash.join,而不用自己写
      import _ from "lodash";
      _.join();
      

source-map [devtool]

1. source-map定义

  • source-map是一个映射关系文件,存储了打包文件中的语句与源文件中语句的对应关系

2. source-map作用

  • 在报错时,会提示源文件的错误行数,而不是打包文件中的错误行数

3. source-map使用

  • 通过设置devtool的值来开启source-map
    module.exports = {
        devtool: "source-map"
    }
    

4. source-map属性值

  • none: 不使用source-map
  • source-map: 生成.map文件,报错精确到某行某列
  • inline: map信息写入到打包文件中
  • cheap: 报错只精确到某行,不精确到某列
  • module: 只管源代码,不管node_modules中的代码
  • eval: 通过eval函数来实现映射,速度快

5. 最佳实践:

  • 开发环境:cheap-module-eval-source-map
  • 生产环境:cheap-module-source-map

自动刷新 [devServer]

1. 方法一:使用--watch参数

  • 功能:监听文件状态,一旦发生改变,则自动重新打包
  • 缺点:无法自动打开浏览器,无法自动刷新页面,无法模拟服务器环境(不可以用ajax请求)
  • 使用:在执行webpack命令时,加上--watch参数
    "script" : {
        "watch": "npx webpack --watch"
    }
    

2. 方法二:WebpackDevServer

  • 功能:监听文件状态,一旦发生改变
  • 优点:自动刷新浏览器,模拟了服务器(即可以用ajax请求)
  • 使用:添加devServer配置
    • contentBase: 服务器服务的目录路径
    • open: 是否自动打开浏览器
    • prot: 设置端口
    • proxy: 设置代理
    export.module = {
        devServer: {
            contentBase: "./dist",
            open: true // 自动打开浏览器,并访问地址
            prot: 61006,
            proxy: {
                "api": "http://localhost:3000"
            }
        }
    }
    
    "script" : {
        "watch": "npx webpack --watch"
        "start": "npx webpack-dev-server"
    }
    

3. Hot Module Replacement(HMR)

  • 说明:热模块重载,需要通过一个插件,配合devServer使用
  • 功能:只重新加载发生改变的文件,未改变的文件
    • 比如更改了css,就只更新css,而不会重新刷新页面,因为index.js没有发生改变,即方便调试css
  • 使用
    const webpack = require("webpack");
    
    module.exports = {
        plugins: [
            new webpack.HotModuleReplacementPlugin()
        ],
        devServer: {
            hot: true, // 开启热模块替换
            hotOnly: true // 开启热模块替换
        }
    }
    
    • hot: 是否开启HMR
    • hotOnly: 是否当HMR失败时也不执行其他操作(刷新全部页面)

Webpack增强功能 [optimazation]

1. Tree Shaking

  • 作用:打包模块时,只打包模块中使用到的对象,即按需打包
  • 前提:Tree Shaking只支持ES Module,因为ES Module是静态引入
  • 配置
    // webpack.config.js
    // mode: development
    module.exports = {
        optionization: {
            usedExports: true
        }
    }
    
    // package.json
    "siderEffects": ["@babel/polly-fill", "*.css"]
    
  • 注意事项
    • 在development模式下,即使使用了Tree Shaking,也只会在打包文件中以注释形式提示只导出了某些对象,但实际还是会导出全部对象,这是为了避免在调试时,source-map发生错误
    • 在producion模式下,使用了Tree Shaking后,只会导出实际使用到的对象
    • 在production模式下,默认开启了Tree Shaking,optionization配置可以不写
    • sideEffecets是为了避免Webpack不打包那种只导入而不使用的模块。比如CSS文件

2. Code Splitting

  • 定义:代码分割,将一个js文件分开为多个文件
  • 作用:当页面业务逻辑发生变化时,会重新加载js文件,而如果没有发生改变的文件由于缓存的存在,而不用重新下载,这样能提高性能
  • 使用
    module.exports = {
        optimazation:  {
            splitChunks: "all"
        }
    }
    

Webapck高级功能

1. 配置文件模块化

  • 将配置文件进行抽离
    • 工程目录
      • webpack.common.js
      • webpack.dev.js
      • webpack.prod.js
    • package.json
      {
          "scripts": {
              "dev": "webpack-dev-server --config webpack.dev.js",
              "build": "webpack --config webpack.prod.js"
          }
      }
      
  • 将配置进行合并
    • 安装webpack-merge
      yarn add webpack-merge
      
    • 使用webpack-merge
      // webpack.dev.js
      const merge = require("webpack-merge");
      const commonConfig = require("./webpack.common.js");
      const devConfig = {}
      module.exports = merge(commonConfig, devConfig);
      
      // webpack.prod.js
      const merge = require("webpack-merge");
      const commonConfig = require("./webpack.common.js");
      const prodConfig = {}
      module.exports = merge(commonConfig, prodConfig);
      

2. Lazy Loading、webpackPreloading和webpackPrefetching

  • JavaScript加载:指的是下载并执行JavaScript代码
  • Lazy Loading
    • 定义:懒加载,即按需加载,默认不加载全部的JavaScript,而是当需要使用时才加载相应的JavaScript
    • 使用:需要使用ES6中的import语句
      • 通过Promise方式
        // 点击时才加载click模块中的代码
        document.addEventListener("clik", () => { 
            import("./click.js").then(
                ({default, func}) => { 
                    func(); 
                }
            ); 
        }); 
        
      • 通过async/await方式
        // 点击时才加载click模块中的代码
        async function Click() {
            const { default, func } = await import("./click.js");
            func();
        }
        
        document.addEventListener("clik", Click);
        
  • webpackPrefetching
    • 使用:用在import语句中
    • 作用:在核心代码加载完成后,空闲时主动进行异步加载,弥补了懒加载的缺点
    • 对比:懒加载是使用时才进行加载,而webpackPrefecting是空闲时进行加载,这样能提高性能
      document.addEventListener("clik", () => {
          import(/* webpakPrefetch: true */ "./click.js")
              .then(({default,func}) => {
                  func();
              });
          }
      );
      
  • webpackPreloading
    • 使用:用在import语句中
    • 作用:在核心代码加载时也一起加载
    • 对比:懒加载是使用时才进行加载,而webpackPreloading是与核心代码一起进行加载
      document.addEventListener("clik", () => {
          import(/* webpakPreloading: true */ "./click.js")
              .then(({default,func}) => {
                  func();
              });
          }
      );
      

3. Webpack与浏览器缓存

  • 在文件名字上加上文件内容的哈希值,从而在文件发生改变时,防止浏览器缓存的影响
    output: {
        filename: '[name].[contenthash].js',
        chunkFilename: '[name].[contenthash].js'
    }
    

Webpack使用常见工具

  • Babel处理ES6
    • // TODO