webpack学习笔记

2024-02-27  本文已影响0人  洛珎

webpack 基本应用

【注意】只过一遍知识点,不再详细代码演示了。

安装和配置 —— 拆分 dev prod 配置,然后 merge

本地服务和代理

处理 ES6

先引入简单的 babel ,polyfill 以后会单独讲

处理样式

基本使用

postcss

处理图片

考虑 base64 格式

webpack 题目

webpack 面试题

babel 面试题

webpack 高级应用

多入口

抽离 & 压缩 css 文件

抽离
把原common里面的css、less配置移到dev下,prod的需要配置module和plugin,如下所示:

压缩

抽离公共代码

场景:一般是公共模块或者第三方模块,多处引用如果不提取,可能会存在代码重复冗余。
common和dev基本不用改动,dev能快速用即可,提取可能还稍显麻烦了

test:/node-module/,表示会进行匹配。比如lodash会安装到node-module里面,当匹配到就符合这个条件
minSize:表示大小限制。比如有的文件很小就几行代码,直接引用即可没必要抽取,所以需要限制一下
minChunks:表示最少复用几次。比如第三方模块,属于可能会在多处引用的情况,所以引用超过一次就提取;但公共模块可能需要2次,如果只在某个地方用到1次就没必要提取

懒加载

webpack不需要做配置,只需要import引入即可

至此,可以总结一下:

处理 React 和 vue

处理react:在.babelrc加上"@babel/preset-env"即可


image.png

module、chunk、bundle区别:

常见 loader 和 plugin

webpack 性能优化

注意:


打包效率

1)优化 babel-loader

{
    test: /\.js$/,
    use: ['babel-loader?cacheDirectory'], // 开启缓存
    include: path.resolve(__dirname, 'src'), // 明确范围
    // // 排除范围,include 和 exclude 两者选一个即可
    // exclude: path.resolve(__dirname, 'node_modules')
},

2)IgnorePlugin 避免引入哪些模块

以常用的 moment 为例。安装 npm i moment -d 并且 import moment from 'moment' 之后,monent 默认将所有语言的 js 都加载进来,使得打包文件过大。可以通过 ignorePlugin 插件忽略 locale 下的语言文件,不打包进来。

plugins: [
    // 忽略 moment 下的 /locale 目录
    new webpack.IgnorePlugin(/\.\/locale/, /moment/)
]
import moment from 'moment'
import 'moment/locale/zh-cn' // 这里手动引入中文语言包
moment.locale('zh-cn')

noParse 避免重复打包

module.noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。 原因是一些库,例如 jQuery 、ChartJS, 它们庞大又没有采用模块化标准,让 Webpack 去解析这些文件耗时又没有意义。

module.exports = {
  module: {
    // 独完整的 `react.min.js` 文件就没有采用模块化
    // 忽略对 `react.min.js` 文件的递归解析处理
    noParse: [/react\.min\.js$/],
  },
};

.min.js基本都是一家模块化处理过的,不需要重新再打包

两者对比一下:

happyPack 多进程打包

【注意】大型项目,构建速度明显变慢时,作用才能明显。否则,反而会有副作用。

webpack 是基于 nodejs 运行,nodejs 是单线程的,happyPack 可以开启多个进程来进行构建,发挥多核 CPU 的优势。

const path = require('path')
const HappyPack = require('happypack')

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
        use: ['happypack/loader?id=babel'],
        exclude: path.resolve(__dirname, 'node_modules')
      }
    ]
  },
  plugins: [
    new HappyPack({
      // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
      id: 'babel',
      // 如何处理 .js 文件,用法和 Loader 配置中一样
      loaders: ['babel-loader?cacheDirectory'],
      // ... 其它配置项
    })
  ]
}

ParallelUglifyPlugin 多进程压缩 js

webpack 默认用内置的 uglifyJS 压缩 js 代码。
大型项目压缩 js 代码时,也可能会慢。可以开启多进程压缩,和 happyPack 同理。

const path = require('path')
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')

module.exports = {
  plugins: [
    // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
    new ParallelUglifyPlugin({
      // 传递给 UglifyJS 的参数
      // (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
      uglifyJS: {
        output: {
          beautify: false, // 最紧凑的输出
          comments: false, // 删除所有的注释
        },
        compress: {
          // 在UglifyJs删除没有用到的代码时不输出警告
          warnings: false,
          // 删除所有的 `console` 语句,可以兼容ie浏览器
          drop_console: true,
          // 内嵌定义了但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        }
      },
    }),
  ],
};

适用场景:项目较大,打包较慢,开启多进程能提高速度。项目较小的情况下,不建议,
因为相对而言进程有开销,反而降低了打包速度

自动刷新

watch 默认关闭。但 webpack-dev-server 和 webpack-dev-middleware 里 Watch 模式默认开启。

先验证下 webpack 是否能默认自动刷新页面 ???

module.export = {
  watch: true, // 开启监听,默认为 false
  // 注意,开启监听之后,webpack-dev-server 会自动开启刷新浏览器!!!

  // 监听配置
  watchOptions: {
    ignored: /node_modules/, // 忽略哪些
    // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
    // 默认为 300ms
    aggregateTimeout: 300,
    // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
    // 默认每隔1000毫秒询问一次
    poll: 1000
  }
}

自动更新

image.png

一般开启devServer就会自动更新(页面会自动刷新)

热更新

操作步骤

const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');

module.exports = {
  entry:{
    // 为每个入口都注入代理客户端
    index:[
        'webpack-dev-server/client?http://localhost:8080/',
        'webpack/hot/dev-server',
        path.join(srcPath, 'index.js')
    ],
    // other 先不改了
  },
  plugins: [
    // 该插件的作用就是实现模块热替换,实际上当启动时带上 `--hot` 参数,会注入该插件,生成 .hot-update.json 文件。
    new HotModuleReplacementPlugin(),
  ],
  devServer:{
    // 告诉 DevServer 要开启模块热替换模式
    hot: true,
  }  
};

js 热替换不生效,是因为我们要自己增加代码逻辑。

// 增加,开启热更新之后的代码逻辑
if (module.hot) {
    module.hot.accept(['./math'], () => {
        const sumRes = sum(10, 20)
        console.log('sumRes in hot', sumRes)
    })
}

最后,热替换切勿用于 prod 环境!!!

DllPlugin

Dll 动态链接库,其中可以包含给其他模块调用的函数和数据。

要给 Web 项目构建接入动态链接库的思想,需要完成以下事情:

为什么给 Web 项目构建接入动态链接库的思想后,会大大提升构建速度呢?

???????
Webpack 已经内置了对动态链接库的支持,需要通过2个内置的插件接入,它们分别是:

打包出 dll 的过程

使用 dll

总结 - 提高构建效率的方法

哪些可用于线上,哪些用于线下

webpack 性能优化

产出代码优化

使用 production

ES6 Module 和 commonjs 的区别

// commonjs
let apiList = require('../config/api.js')
if (isDev) {
    // 可以动态引入,执行时引入
    apiList = require('../config/api_dev.js')
}
import apiList from '../config/api.js'
if (isDev) {
    // 编译时报错,只能静态引入
    import apiList from '../config/api_dev.js'
}

小图片 base64 编码

image.png

bundle 加 hash

image.png

使用 CDN加速

1、配置 publicPath


image.png
产物dist是cdn开头: image.png
2、将产物地址上传到cdn,成功后就能访问了

提取公共改代码

懒加载

使用production

开启mode:production

scope hosting 将多个函数合并到一个函数中

作用:代码体积更小、创建函数作用域更少、代码可读性更好
使用前后的对比,使用的好处、
优化前:
源码:


image.png

优化前打包后产物:


image.png

会各自创建函数作用域,代码量较大

优化后打包产物:


image.png

体积更小

配置如下:

//1.引入插件
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')

module.exports = {
  resolve: {
    // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 开启 Scope Hoisting
    new ModuleConcatenationPlugin(),
  ]
}

同时,考虑到 Scope Hoisting 依赖源码需采用 ES6 模块化语法,还需要配置 mainFields。因为大部分 Npm 中的第三方库采用了 CommonJS 语法,但部分库会同时提供 ES6 模块化的代码,为了充分发挥 Scope Hoisting 的作用。

webpack 原理和二次开发

这部分是面试的加分项,大家不要深究细节。即便面试被问到,实际工作中应用的概率也非常小。
webpack 本身就是一个工具,只是用来打包构建。而且发展多年已经成熟完善,日常的开发场景都能满足。


webpack 构建流程

几个核心概念

Webpack 的构建流程可以分为以下三大阶段:


开发 loader

less-loader 为例,回顾一下使用规则

{
    test: /\.less$/,
    // 注意顺序
    loader: ['style-loader', 'css-loader', 'less-loader']
}

所以,loader 的作用:

所以一个 loader 的基本开发模式:

const less = require('node-less')
module.exports = function(source) {
  // source 即 less 代码,需要返回 css 代码
  return less(source)
}

以上是 loader 的基本开发方式,实际开发中可能还会有更多要求

// options
const loaderUtils = require('loader-utils')
module.exports = function(source) {
  // 获取 loader 的 options
  const options = loaderUtils.getOptions(this)
  return source // 示例,直接返回 source 了
}
// 异步 loader
module.exports = function(source) {
    // 使用异步 loader
    const callback = this.async()
    // 执行异步函数,如读取文件
    someAsyncFn(source, function(err, result, sourceMaps, ast) {
        // 通过 callback 返回异步执行后的结果
        callback(err, result, sourceMaps, ast)
    })
}
// 缓存
module.exports = function(source) {
  // 关闭该 Loader 的缓存功能(webpack 默认开启缓存)
  this.cacheable(false)
  return source
}

babel

本节主要解决以下问题,相信很多同学都很懵

初始化环境

使用 polyfill

什么是 babel-polyfill

基本使用

按需加载

使用 runtime

babel-polyfill 的问题 —— 会污染全局变量

// 源代码
Promise.resolve(100).then(data => data);
[10, 20, 30].includes(20);

// 结果 —— 可以看到,Promise 和 includes 都未改动,因为以注入全局变量了
"use strict";
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");

Promise.resolve(100).then(function (data) {
  return data;
});
[10, 20, 30].includes(20);

使用 babel-runtime


image.png

总结

前端为什么要进行打包和构建?
代码层面:
1)体积更小(Tree-Sharking、压缩、合并,加载更快)
2)编译高级语言或语法(TS、ES6+、模块化、scss)
3)兼容性和错误检查(Polyfill、postcss、eslint)

工程化/团队规范方面:
统一高效的开发环境
统一的构建流程和产出标准
集成公司构建规范(提测、上线等)

loader和plugin的区别?
loader模块转换器,如less-》css
plugin扩展插件,如HtmlWebpackPlugin

常用loader和plugin有哪些?

babel和webpack的区别?

如何产出一个lib?

参考dll, image.png

babel-polyfill和babel-runtime的区别
-babel-polyfill会污染全局,而babel-runtime不会污染全局
产出第三方lib要用babel-runtime

webpack如何实现懒加载
import()

上一篇下一篇

猜你喜欢

热点阅读