webpack4学习

154 阅读5分钟

1. Webpack简介

1.1 Webpack是什么?

Webpack是一种前端资源构建工具,是一个静态模块打包器(module bundler)。 在Webpack看来,前端的所有资源文件(js/json/css/img/less...)都会作为模块处理。它根据模块的依赖关系构建一个依赖图,进行静态分析,打包生成对应的静态资源(bundle)。

为什么使用webpack?

现代JavaScript应用程序的最大特点可能就是模块化了,这里面涉及很多新技术,比如ES6,Less等,但是不是所有浏览器都适用这些技术,webpack的作用是将代码转化为直接在浏览器上运行的代码。

1.2 核心概念

1.2.1 入口(entry)

页面的入口文件,打包从哪个文件开始。有几个入口文件,就有几个依赖图。 入口起点指示webpack应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack会找出有哪些模块和库是入口起点(直接和间接)依赖的。
也可以在webpack.config.js中配置入口起点文件: 3种写法:

  1. 字符串
module.exports = {
    entry: './path/file.js'
}
  1. 对象
module.exports = {
    entry: {
        index: path.resolve(__dirname, './src/main.js')
    }
}
  1. 数组(多个入口文件)
module.exports = {
    entry: ['./src/index.js', './src/main.js']
}

为什么会有多个入口文件?

  1. 分离app(应用程序)和vendor(第三方库)入口
  2. 多页面应用

1.2.2 输出(output)

output属性告诉webpack在哪里输出它所创建的bundle,以及如何命名这些文件。默认值:./dist/main.js,其它生成文件默认放在./dist文件夹中。
也可以在webpack.config.js中指定一个output字段:

const path = require('path')
module.exports = {
    entry: './path/file.js'
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'mybundle.js'
    }
}

当有多个入口文件时,这样配置:

const path = require('path')
module.exports = {
    entry: './path/file.js'
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
}

使用CDN和hash:这里我就不懂了,先写在这

module.exports = {
    output: {
        path: '/home/proj/cdn/assets/[hash]',
        publicPath: 'http://cdn.example.com/assets/[hash]/'
    }
}

1.2.3 loader

webpack只能理解JS文件和JSON文件。 loader使得webpack能够处理其它类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。 在更高层面,在 webpack 的配置中 loader 有两个属性:

  • test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
  • use 属性,表示进行转换时,应该使用哪个 loader。 在webpack.config.js中配置:
const path = require('path')
module.exports = {
    output: {
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {test: /\.txt$/, use: 'raw-loader'}
        ]
    }
}

告诉webpack编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先使用raw-loader转换一下。

1.2.4 插件(plugin)

loader用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量等。
通过在webpack.config.js文件里配置,使用plugins。

const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
module.exports = {
  module: {
    rules: [
      { test: /.txt$/, use: 'raw-loader' }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

1.2.5 模式(mode)

通过选择 developmentproduction 或 none 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production
用法:

  1. webpack.config.js文件里配置:
module.exports = {
    mode: 'production'
}
  1. 从CLI参数中传递:
webpack --mode=production

1.2.6 浏览器兼容(browser compatibility)

webpack支持所有符合ES5标准的浏览器(不支持IE8及以下版本)。 webpack 的 import() 和 require.ensure() 需要 Promise。如果你想要支持旧版本浏览器,在使用这些表达式之前,还需要 提前加载 polyfill

2. 入口起点

在webpack.config.js文件中有多种配置entry属性的方式。

2.1 单个入口(简写)语法

用法:entry: string|Array<string>
webpack.config.js文件简写配置:

module.exports = {
    entry: './path/entry.js'
}

详细写法:

module.exports = {
    entry{
        main: './path/entry.js'
    }
}

2.2 对象语法

用法:entry: {[entryChunkName: string]: string|Array<string>}
webpack.config.js文件中配置:

module.exports = {
    entry: {
        app: './src/app.js',
        adminApp: './src/adminApp.js'
    }
}

入口对象的完整写法

  • dependOn:当前入口所依赖的入口。它们必须在该入口被加载前加载
  • filename:指定要输出的文件名称
  • import:启动时需加载的模块
  • library:指定library选项,为当前entry构建一个library
  • runtime:运行时 chunk 的名字。如果设置了,就会创建一个新的运行时 chunk。在 webpack 5.43.0 之后可将其设为 false 以避免一个新的运行时 chunk。
  • publicPath:当该入口的输出文件在浏览器中被引用时,为它们指定一个公共URL地址

注意:

  • runtimedependOn不应在同一个入口上同时使用
  • runtime不能指向已存在的入口名称
  • dependOn不能循环引用

2.3 常见场景

分离app(应用程序)和vendeor(第三方库)入口

多页面应用程序

webpack.config.js文件配置:

module.exports = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js'
  }
};

3. 输出

loader用于对模块的源代码进行转换。loader可以使得你在import或加载模块时预处理文件。 loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript 或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!

2.1 使用loader

1. 配置:在webpack.config.js文件中指定loader

// webpack.config.js
module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    { loader: 'style-loader' },
                    { 
                        loader: 'css-loader',
                        options: {
                            modules: true
                        }
                    }
                ]
            }
        ]
    }
}

2. 内联:在每个import语句中显示指定loader

直接在import语句中指定loader。使用 ! 将资源中的 loader 分开。每个部分都会相对于当前目录解析。

import Styles from 'style-loader!css-loader?modules!./styles.css';

选项可以传递查询参数,例如 ?key=value&foo=bar,或者一个 JSON 对象,例如 ?{"key":"value","foo":"bar"}

3. CLI:在shell命令中指定它们

webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'

2.2 loader特性

  1. loader支持链式传递。
  2. loader可以是同步的,可以是异步的。
  3. loader运行在Node.js中,并且能够执行任何 Node.js 能做到的操作。
  4. loader可以通过options对象配置(这个配置项都有哪些属性?网上怎么查不到) 我见过的options配置:
module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.(png|jpg|jpeg)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8 * 1024,
                            // 关掉es6的模块化解析
                            esModule: false,
                            name: '[hash:10].[ext]'
                        }
                    }
                ]
            }
        ]
    }
}
  1. 插件可以为loader带来更多特性
  2. loader能够产生额外的任意文件

4. 插件(plugin)

插件的目的在于解决loader无法实现的其他事

4.1 剖析

webpack插件是一个具有apply方法的JavaScript对象。apply方法会被webpack compiler调用,并且compiler对象可在整个编译生命周期访问。

4.2 配置

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装
const webpack = require('webpack'); //访问内置的插件
const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    filename: 'my-first-webpack.bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,
        use: 'babel-loader'
      }
    ]
  },
  plugins: [
    new webpack.ProgressPlugin(),
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

4.3 Node API

在使用Node API时,还可以通过配置中的plugins属性传入插件

const webpack = require('webpack'); //访问 webpack 运行时(runtime)
const configuration = require('./webpack.config.js');

let compiler = webpack(configuration);

new webpack.ProgressPlugin().apply(compiler);

compiler.run(function(err, stats) {
  // ...
});

5. webpakc性能优化

5.1 开发环境性能优化

1. 优化打包构建速度

2. 优化代码调试

5.2 生产环境性能优化

1. 优化打包构建速度

2. 优化代码运行性能

5.3 模块热替换(Module Hot Replacement)

模块热替换允许运行时更新(替换、添加或删除)所有类型的模块,而无需完全刷新。 主要通过以下几个方式,来显著加快开发速度:

  • 保留在完全重新加载页面期间丢失的应用程序状态
  • 只更新变更内容,以节省宝贵的开发时间
  • 在源代码中对CSS/JS进行修改,会立刻在浏览器中进行更新

作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块),极大提升构建速度。

样式文件:可以使用HMR功能,因为style-loader内部实现了 js文件:默认不使用HMR功能;-> 修改js代码,添加👇的代码,注意HMR对js的处理,只能处理非入口js文件的其它文件。 html文件:默认不使用HMR功能,这个不用热更新

if(module.hot){
  module.hot.accept('./js/print.js', function(){
    console.log(1)
  })
}

10. 项目中用过的配置

10.1 配置过的插件(plugins)

10.1.1 html-webpack-plugin

说明: 该插件将生成一个HTML5文件,在body中使用script标签引入webpack生成的所有bundle。 安装:

npm install --save-dev html-webpack-plugin

用法:

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js',
  },
  plugins: [new HtmlWebpackPlugin({
      template: path.resolve(__dirname, './src/index.html),
      // 压缩html
      minify: {
          collapseWhitespace: true,
          removeComments: true
      }
  })],
};

如果有多个webpack入口,它们都会在已生成的HTML文件中的<script>标签内引入。 如果在 webpack 的输出中有任何 CSS 资源(例如,使用 MiniCssExtractPlugin 提取的 CSS),那么这些资源也会在 HTML 文件 <head> 元素中的 <link> 标签内引入。

参考🔗:HtmlWebpackPlugin

10.1.2 mini-css-extract-plugin

说明: 将css单独打包成一个文件的插件,它为每个包含cssjs文件都创建一个css文件。它支持csssourceMaps的按需加载。 安装:

npm install --save-dev mini-css-extract-plugin

用法:

// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    // ...
    module: {
        rules: [
            test: /\.css$/,
            use: [
                MiniCssExtractPlugin.loader,
                'css-loader'
            ]
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
          filename: 'css/style.css'
        })
    ]
}

10.1.3 postcss-preset-env

说明: postcss-preset-env允许我们将现代CSS转换为大多数浏览器可以理解的内容,即兼容浏览器,具体兼容哪些浏览器需要在package.json文件中配置browserslist字段。 安装: 需要跟postcss一起使用。

npm install postcss-preset-env --save-dev

用法:

// webpack.config.js
module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            indent: 'postcss',
                            plugins: () => [
                                require('postcss-preset-env')()
                            ]
                        }
                    }
                ]
            }
        ]
    }
}

10.2 loader

10.2.1 postcss-loader

说明: 使用postcss处理css的loader 安装:

npm install --save-dev postcss-loader

用法:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /.css$/i,
        use: [
          "style-loader",
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  [
                    "postcss-preset-env",
                    {
                      // Options
                    },
                  ],
                ],
              },
            },
          },
        ],
      },
    ],
  },
};