webpack从入门到精通(一)

1,004 阅读51分钟

一、什么是WebPack?

简单地说,Webpack其最核心的功能就是 解决模板之间的依赖,把各个模块按照特定的规则和顺序组织在一起,最终合并成一个JS文件(比如bundle.js)。这个整个过程也常常被称为是模块打包。

WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到 JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Sass, TypeScript等),并将其转换和打包为合适的格式供浏览器使用。

二、为什么要用Webpack

在开发Web页面或Web应用程序的时候,都习惯性的将不同资源放置在不同的文件目录之中,比如图片放置在images(或img)下,样式文件放置在styles(或css)中,脚本文件放在js和模板文件放置在pages中。一直以来,发布的时候都会一次性的将所有资源打包发布,不管这些资源用到了还是没用到(事实上很多时候自己都分不清楚哪资源被使用)。用一句话来描述就是:依赖太复杂,太混乱,无法维护和有效跟踪。比如哪个样式文件引用了a.img,哪个样式文件引用了b.img;另外页面到底是引用了a.css呢还是b.css呢?

而Webpack这样的工具却能很多好的解决它们之间的依赖关系,使其打包后的结果能运行在浏览器上。其目前的工作方式主要被分为两种:

将存在依赖关系的模块按照特定规则合并成为单个.js文件,一次性全部加载进页面 在页面初始时加载一个入口模块,其他模块异步加载

2.1 Webpack的主要优势:

webpack-dev-server搭建本地环境,进行热更新

• 预处理(Less,Sass,ES6,TypeScript,……) • 图片添加hash,方便线上CDN缓存 • 自动处理CSS3属性前缀 • 单文件,多文件打包 。。。。 • 有完备的代码分割解决方案 • 模块化开发(import,require) ,支持多种模块标准 • 压缩js代码 • 复制 • 按需加载

相比于Parcel、Rollup具有同等功能的工具而言,Webpack还具有其他的优势:

  • Webpack支持多种模块标准: 这对于一些同时使用多种模块标准的工程非常有用,Webpack会帮我们处理好不同类型模块之间的依赖关系

  • Webpack有完备的代码分割解决方案: 它可以分割打包后的资源,首屏只加载必要的部分,不太重要的功能放到后面动态地加载

  • Webpack可以处理各种类型的资源: 除了JavaScript之外,Webpack还可以处理样式、模板、图片等资源。开发者要做的只是将之些资源导入,而无需关注其他. 另外,Webpack还拥有一个强大的社区。

2.2 Webpack4与Webpack3版本的区别:

1. 增加了mode属性,可以是 development 或 production

"scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
}

通过mode, 你可以轻松设置打包环境。

如果你将 mode 设置成 development,你将获得最好的开发阶段体验。这得益于webpack针对开发模式提供的特性:

  • 浏览器调试工具
  • 注释、开发阶段的详细错误日志和提示
  • 快速和优化的增量构建机制

如果你将mode设置成了 production, webpack将会专注项目的部署,包括以下特性:

  • 开启所有的优化代码
  • 更小的bundle大小
  • 去除掉只在开发阶段运行的代码
  • Scope hoisting和Tree-shaking

2. webpack4在loader,optimize上进行了很多改动

module.loaders改为module.rules
module.loaders.loader改成module.loaders.use
loader的'-loader'均不可省略

3. 插件和优化

webpack4删除了合并相同的文件CommonsChunkPlugin插件,它使用内置API optimization.splitChunks 和 optimization.runtimeChunk,这意味着webpack会默认为你生成共享的代码块。

其它插件变化如下:

NoEmitOnErrorsPlugin 废弃,使用optimization.noEmitOnErrors替代,在生产环境中默认开启该插件。
ModuleConcatenationPlugin 废弃,使用optimization.concatenateModules替代,在生产环境默认开启该插件。
NamedModulesPlugin 废弃,使用optimization.namedModules替代,在生产环境默认开启。
uglifyjs-webpack-plugin升级到了v1.0版本, 默认开启缓存和并行功能。

之前是这样:

plugins : [
    new webpack.optimize.CommonsChunkPlugin({
        name : 'main',
        children : true,
        minChunks : 2,
    })]

此段代码已经在webpack4的高版本里面被移除 换成了如下的配置选项:

optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    name: "commons",
                    chunks: "initial",
                    minChunks: 2
                }
            }
        }
}

三、Webpack5展望

npm install —save-dev webpack@next

官方描述:

  1. 使用持久化缓存提高构建性能;
  2. 使用更好的算法和默认值改进长期缓存(long-term caching);
  3. 清理内部结构而不引入任何破坏性的变化;
  4. 引入一些breaking changes,以便尽可能长的使用v5版本。

通俗版描述:

  1. 减小打包后的文件体积
  2. 按需加载支持文件名模式
  3. 使用long-term caching解决生产环境下moduleIds & chunkIds变化的问题
  4. 使用cache: {type: "filesystem"}配置实现持久化缓存,提高构建速度
  5. 优化minSize&maxSize的配置方式
  6. Node.js polyfills 自动加载功能被移除

四、从零开始构建你自己的开发环境

为了更好的理解Webpack能帮我们做什么,我打算从零开始构建一个属于自己的开发环境。可能在后面的内容中会涉及到很多关键词,比如Webpack、loaders、Babel、sourcemaps、Vue、React、TypeScript,CSS Modules等等。

配置版本说明:

node: v12.16.3
Yarn: 1.16.0
Webpack: 4.44.1
Webpack-cli: 3.3.12

演示的系统是window , 以及node 的下载,安装成以后,会自带npm命令,如果想体验yarn 命令 可以通过npm install yarn -g 或者下载安装

4.1 环境安装:

当使用webpack4时,确保使用 Node.js的版本 >= 8.9.4。因为webpack4使用了很多JS新的语法,它们在新版本的 v8 里经过了优化。

  • 安装命令yarn或者npm的选择:

yarn的安装和使用参考:blog.csdn.net/x550392236/…

  • 安装webpack以及webpack-cli (不推荐)
yarn global add webpack webpack-cli
或者: npm install webpack webpack-cli -g 

可以帮助我们在命令⾏行⾥使用webpack等相关指令 webpack -v

  • 卸载
npm uninstall webpack webpack-cli -g 或者
yarn global remove webpack webpack-cli
  • 局部安装 项目内安装 (推荐)
yarn add webpack webpack-cli --dev/-D  或者
npm install webpack webpack-cli --save--dev/-D

--dev/-D 指安装到devDependencies:开发时的依赖包 --save/-S dependencies:运行程序时的依赖包

webpack -v  //command not found 默认在全局环境中查找
npx webpack -v// npx帮助我们在项⽬目中的node_modules里查找webpack

  • 指定版本安装:

npm info webpack//查看webpack的历史发布信息 npm install webpack@x.xx webpack-cli -D npm i -D 是 npm install --save-dev 的简写,是指安装模块并保存到

注意,在命令终端使用npm i安装依赖关系时,如果带后缀 -D(或--save-dev) 安装的包会记录在"devDependencies"下;如果使用 --save后缀安装的包会记录在"dependencies"下。

4.2 . webpack 配置文件

当我们使⽤用npx webpack index.js时,表示的是使⽤用webpack处理理打包,名为index.js的⼊口模块。默 认放在当前⽬目录下的dist⽬目录,打包后的模块名称是main.js,当然我们也可以修改 webpack有默认的配置⽂文件,叫webpack.config.js,我们可以对这个文件进行修改,进行行个性化配置

  • 不使⽤用默认的配置⽂文件: webpackconfig.js
npx webpack --config webpackconfig.js 
//指定webpack使⽤用webpack.config.js文件来作为 配置⽂文件并执行
  • 修改package.json scripts字段:有过vue react开发经验的同学 习惯使⽤用npm run来启动,我们也 可以修改下
"scripts":{  
    "bundle":"webpack"
    //这个地⽅不要添加npx ,因为npm run执⾏行的命令,会优先使⽤用项目工程里的包,效果和npx一样 
    }
npm run bundle 

4.3. 初始化项目,生成package.json

进入到新创建的项目目录下,执行 yarn init -y 或者npm init 生成package.json 首先在你的本地创建一个项目,比如我这里创建了一个webpack-sample项目: mkdir webpack-sample && cd webpack-sample

进入到新创建的项目目录下,执行yarn init或者yarn init -y命令来初始化项目,执行完该命令之后,在你的命令终端会命令询问,你可以根据你自己的需要去输入你想要的内容,或者一路Enter键执行下去:

此时你的项目根目录下会增加一些文件和文件夹:

|--webpack-sample/  |----package.json  |----package-lock.json

其中package.json文件里将包含一些项目信息: 而package-lock.json文件是当 node_modules/ 或 package.json 发生变化时自动生成的文件,它的主要功能是 确定当前安装的包的依赖,以便后续重新安装的时候生成相同的依赖,而忽略项目开发过程中有些依赖已经发生的更新。

4.4. 安装Webpack和初始配置Webpack

在这一步,先来安装Webpack。执行下面的命令安装Webpack配置所需要的包:

yarn add webpack webpack-cli webpack-dev-server --dev

此时打开package.json文件,你会发现在文件中有一个新增项 devDependencies:

{
     // 其他项信息在这省略,详细请查看该文件 
    "devDependencies": { 
    "webpack""^4.35.0""webpack-cli""^3.3.5",
     "webpack-dev-server""^3.7.2"
 } 
}

为了验证Webpack是否能正常工作,这个时候我们需要创建一些新的文件。在webpack-sample根目录下创建/src目录,并且在该目录下创建一个index.js文件:

mkdir src && cd src && touch index.js

我们在新创建的/src/index.js文件下添加一行最简单的JavaScript代码:

console.log("Hello, Webpack!(^_^)~~")

保存之后回到命令终端,第一次执行有关于Webpack相关的命令:

webpack src/index.js --output dist/bundle.js

执行完上面的命令后,如果看到下图这样的结果,那么要恭喜你,Webpack的安装已经成功,你可以在你的命令终端执行有关于Webpack相关的命令:

回到项目中,会发现项目根目下自动创建了一个/dist目录,而且该目录下包含了一个bundle.js文件:

执行完上面的命令之后,可以看到有相关的警告信息。那是因为Webpack4增加了mode属性,用来表示不同的环境。mode模式具有development,production 和 none三个值,其默认值是production 。也就是说,在执行上面的命令的时候,我们可以带上相应的mode属性的值,比如说,设置none来禁用任何默认行为

npx webpack src/index.js --output dist/bundle.js --mode none

执行到这里,只知道我们可以运行Webpack相关命令。并不知道/src/index.js的代码是否打包到/dist/bundle.js中。为此,我们可以在/dist目录下创建一个index.html

cd dist && touch index.html

并且将生成出来的bundle.js引入到新创建的index.html:

在浏览器中打开/dist/index.html,或者在命令行中执行:

yarn global add http-server   或者  npm i -g http-server 
http-server dist

http-server是一个启动服务器的npm包,执行上面的命令之后,就可以在浏览器中访问http://127.0.0.1:8080/(访问的/dist/index.html),在浏览器的console.log控制台中,可以看到src/index.js的脚本输出的值:

或者通过vscode的插件,安装一个 live Server 也可以直接在vscode里直接起服务

不过,当你需要构建的东西越复杂,需要的标志就会越多。在某种程度上说,就会变得难以控制。这个时候我们就需要一个文件来管理这些配置。接下来我们需要创建一个webpack.config.js这样的一个文件,用来配置Webpack要做的事情。注意,这个文件是一个node.js文件,所以你可以在任何节点文件中执行任何你能够执行的操作。你也可以写成json文件,但是node文件更强大一些。

首先创建Webpack的配置文件,在webpack-sample根目录下创建一个/build目录,然后在该目录下添加一个名为webpack.config.js文件:

mkdir build && cd build && touch webpack.config.js

执行完上面的命令之后,你会发现你的项目文件目录结构变成下面这样了:

这个时候,新创建的webpack.config.js文件里面是一片空白,它就是Webpack的配置文件,将会导出一个对象的JavaScript文件。我们需要在这个文件中添加一些配置:

var webpack = require('webpack'); 
var path = require('path'); 
var DIST_PATH = path.resolve(__dirname, '../dist');  
// 声明/dist的路径 为成绝对路径
module.exports = { 
    // 入口JS路径 
    // 指示Webpack应该使用哪个模块,来作为构建其内部依赖图的开始 
    entry: path.resolve(__dirname,'../src/index.js'), 
        // 支持单文件,多文件打包
         // entry: './src/index.js',   //方式一
         // entry: ['./src/index.js','./src/main.js'], //方法二

        //entry: {
        // index:'./src/index.js',
        // main:'./src/main.js'
        // }

    // 编译输出的JS入路径 
    // 告诉Webpack在哪里输出它所创建的bundle,以及如何命名这些文件 
    output: { 
        path: DIST_PATH, // 创建的bundle生成到哪里
        filename: 'bundle.js', // 创建的bundle的名称
    }, 
    // 模块解析 
    module: { }, 
    // 插件
    plugins: [ ], 
    // 开发服务器 
    devServer: { }
}

Webpack配置是标准的 Node.js CommonJS模块,它通过require来引入其他模块,通过module.exports导出模块,由Webpack根据对象定义属性进行解析。

上面很简单,到目前为止只通过entry设置了入口起点,然后通过output配置了打包文件输出的目的地和方式。你可能也发现了,在配置文件中还有module、plugins和devServer没有添加任何东西。不需要太急,后面会一步一步带着大家把这里的内容补全的,而且随着配置的东西越来越多,整个webpack.config.js也会更变越复杂。

完成webpack.config.js的基础配置之后,回到package.json文件,并在"scripts"下添加配置如下

package.json

 { 
    // ... 
    "scripts": { 
        "build": "webpack --config ./build/webpack.config.js"
    },
 }

这样做的,为让我们直接在命令终端执行相关的命令就可以实现相应的功能。比如上面配置的build,在命令终端执行: ⇒ npm run build

面的命令执行的效果前面提到的webpack src/index.js --output dist/bundle.js --mode none等同。同样有警告信息,主要是mode的配置没有添加。在上面的配置中添加:

{ 
"scripts": { 
    "build""webpack --config ./build/webpack.config.js --mode production", 
    "test""echo \"Error: no test specified\" && exit 1" 
    }, 
}

再次执行npm run build,不会再有警告信息。你可以试着修改/src/index.js的代码: alert(`Hello, Webpack! Let's Go`); 重新编译之后,打开/dist/index.html你会发现浏览器会弹出alert()框:

为了开发方便,不可能通过http-server来启用服务。我们可以把这部分事件放到开发服务器中来做,对应的就是devServer,所以我们接着在webpack.config.js中添加devServer相关的配置:

五、 webpack-dev-server快速搭建本地运行环境

webpack-dev-server是一个用来快速搭建本地运行环境的工具。webpack-dev-server是一个小型的Node.js Express服务器,它使用webpack-dev-middleware来服务于webpack的包,除此自外,它还有一个通过Sock.js来连接到服务器的微型运行时.

命令简单webpack-dev-server或配置命令脚本快捷运行,模拟服务器运行情况,进行上线前调试。

安装: yarn add webpack-dev-server --dev

5.1 webpack-dev-server和webpack-dev-middleware的区别

  • webpack-dev-server

webpack-dev-server实际上相当于启用了一个express的Http服务器+调用webpack-dev-middleware。它的作用主要是用来伺服资源文件。这个Http服务器和client使用了websocket通讯协议,原始文件作出改动后,webpack-dev-server会用webpack实时的编译,再用webpack-dev-middleware将webpack编译后文件会输出到内存中。适合纯前端项目,很难编写后端服务,进行整合。

  • webpack-dev-middleware

webpack-dev-middleware输出的文件存在于内存中。你定义了 webpack.config,webpack 就能据此梳理出entry和output模块的关系脉络,而 webpack-dev-middleware 就在此基础上形成一个文件映射系统,每当应用程序请求一个文件,它匹配到了就把内存中缓存的对应结果以文件的格式返回给你,反之则进入到下一个中间件。

因为是内存型文件系统,所以重建速度非常快,很适合于开发阶段用作静态资源服务器;因为 webpack 可以把任何一种资源都当作是模块来处理,因此能向客户端反馈各种格式的资源,所以可以替代HTTP 服务器。事实上,大多数 webpack 用户用过的 webpack-dev-server 就是一个 express+webpack-dev-middleware 的实现。二者的区别仅在于 webpack-dev-server 是封装好的,除了 webpack.config 和命令行参数之外,很难去做定制型开发。而 webpack-dev-middleware 是中间件,可以编写自己的后端服务然后把它整合进来,相对而言比较灵活自由。

  • webpack-hot-middleware

是一个结合webpack-dev-middleware使用的middleware,它可以实现浏览器的无刷新更新(hot reload),这也是webpack文档里常说的HMR(Hot Module Replacement)。HMR和热加载的区别是:热加载是刷新整个页面。

webpack.config.js 


// 开发服务器 
devServer: { 
    hottrue// 热更新,无需手动刷新 
    contentBase: DIST_PATH//热启动文件所指向的文件路径
    // host: '0.0.0.0', // host地址 
    port8080// 服务器端口 
    historyApiFallbacktrue// 该选项的作用所用404都连接到index.html 
    proxy: { 
        "/api""http://localhost:3000" 
        // 代理到后端的服务地址,会拦截所有以api开头的请求地址
     } 
}

有关于devServer更详细的配置参数描述

devServer:{ 
    port:7788,
    //控制端口 
    open:true 
    //是否自动打开默认浏览器
    headers ://在所有响应中添加首部内容
    historyApiFallback:true //使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html
    host:'0.0.0.0'  //指定使用一个 host。默认是 localhost。如果你希望服务器外部可访问,指定如下
    hot:true  //启用 webpack 的模块热替换特性
    https:true  //默认情况下,dev-server 通过 HTTP 提供服务。也可以选择带有 HTTPS 的 HTTP/2 提供服务
    Info: true //是否要输出一些打包信息
    noInfo: true  //启用 noInfo 后,诸如「那些显示的 webpack 包(bundle)信息」的消息将被隐藏
    proxy: //这样启用代理 ,https://www.webpackjs.com/configuration/dev-server/#devserver-proxy 
    progress : //将运行进度输出到控制台
    publicPath : //此路径下的打包文件可在浏览器中访问,默认 publicPath是 "/"
    //https://www.webpackjs.com/configuration/dev-server/#devserver-publicpath-
    useLocalIp: true //是否在打包的时候用自己的IP
    watchContentBase:true  //告诉服务器监视devserver.contentbase选项提供的文件。文件更改将触发整页重新加载
}

和build类似,需要在package.json的scripts中添加相关的命令:

package.json

"scripts": { 
    "build": "webpack --config ./build/webpack.config.js --mode production""dev": "webpack-dev-server --config ./build/webpack.config.js ""test": "echo \"Error: no test specified\" && exit 1"
 },

保存所有文件,在命令行中执行npm run dev就可以启动服务器:

你可以验证一下,修改/src/index.js:

document.addEventListener('DOMContentLoaded', () =>const h1Ele = document.createElement('h1'); 
    document.body.append(h1Ele); 
    h1Ele.innerText'Hello Webpack (^_^)' 
    h1Ele.style.color'#f46';
 })

保存该文件之后,浏览器会立刻刷新,你将看到修改之后的变化:

六. 多文件打包,glob配置动态入口

安装:yarn add glob --dev

const glob = require("glob");
const htmls = glob.sync('src/components/**/*.html'); 

扫描出入口页面模板的路径, 如src/components/index/index.html, 存放在 htmls 对象里

//入口文件

var SRC_PATH = path.resolve(__dirname,'../src');
var newEntries = {};
// var files = glob.sync(path.join(SRC_PATH,'js/*.js')); // 方式一
var files = glob.sync(SRC_PATH+'/js/*.js');  //方式二

files.forEach(function(file,index){
//    var substr =  file.split('/').pop().split('.')[0];
   var substr = file.match(/src\/js\/(\S*)\.js/)[1];
   newEntries[substr] = file;
})

多文件的时候,需要修改output输出文件里的 动态文件配置为[name]

 [\s]---表示,只要出现空白就匹配;
 [\S]---表示,非空白就匹配;

七. hash、chunkhash、contenthash区别

hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。但是在实际使用的时候,这几种hash计算还是有一定区别。 [hash:8] 可以用来控制输出多少位的hash

  • Hash

Hash: hash是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值 filename: 'bundle.[name].[hash].js'

  • chunkhash

chunkhash: 采用hash计算的话,每一次构建后生成的哈希值都不一样,即使文件内容压根没有改变。这样子是没办法实现缓存效果,我们需要换另一种哈希值计算方式,即chunkhash。

chunkhash和hash不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。

filename: 'bundle.[name].[chunkhash].js'

  • contenthash

contenthash: 在chunkhash的例子,我们可以看到由于index.css被index.js引用了,所以共用相同的chunkhash值。但是这样子有个问题,如果index.js更改了代码,css文件就算内容没有任何改变,由于是该模块发生了改变,导致css文件会重复构建。 这个时候,我们可以使用extra-text-webpack-plugin里的contenthash值,保证即使css文件所处的模块里就算其他文件内容改变,只要css文件内容不变,那么不会重复构建。

plugins:[
 new extractTextPlugin('../css/bundle.[name].[contenthash].css')
 ]

八. 打包之前删除某个文件夹

rimraf插件

安装:yarn add rimraf --dev

package.json的script直接使用,比如:
"build""rimraf dist && webpack --config ./build/webpack.config.js --mode production",
或者:
 "build""rm -rf dist && webpack --config ./build/webpack.config.js --mode production"

node常用命令

touch 新建文件
rm -f 删除文件 mkdir 新建文件夹 rm -rf 删除文件夹

九. 优化Webpack配置

开发和生产环境相关的配置都集成在webpack.config.js一个文件中。为了更好的维护代码,把webpack.config.js拆分成三个部分:

  • 公共配置: 把开发和生产环境需要的配置都集中到公共配置文件中,即webpack.common.js
  • 开发环境配置: 把开发环境需要的相关配置放置到webpack.dev.js
  • 生产环境配置: 把生产环境需要的相关配置放置到webpack.prod.js

先在/build目录下创建上面提到的三个配置文件。在命令终端执行下面的命令即可:

cd build && touch webpack.common.js webpack.dev.js webpack.prod.js

这个时候,整个项目目录结构变成下图这样:

遗留下来的webpack.config.js文件将会从/build目录中移除。

Object.assign 合并对象

为了更好的管理和维护这三个文件,需要安装一个webpack-merge插件: yarn add webpack-merge --dev 或者 npm i webpack-merge -D 执行完上面的命令之后,package.json文件中的devDependencies会增加

接下来分别给webpack.common.js、webpack.dev.js和webpack.prod.js文件添加相关的配置:

9.1 Webpack公共配置

在公共配置文件webpack.common.js文件中添加相应的配置:

const webpack = require('webpack');
const path = require('path'); 
const DIST_PATH = path.resolve(__dirname, '../dist/'); 
// 声明/dist的路径 
module.exports = {
     // 入口JS路径 
    // 指示Webpack应该使用哪个模块,来作为构建其内部依赖图的开始 
    entry: path.resolve(__dirname,'../src/index.js'), 
        // 编译输出的JS入路径 
        // 告诉Webpack在哪里输出它所创建的bundle,以及如何命名这些文件 
    output: { 
        path: DIST_PATH,
         // 创建的bundle生成到哪里 
        filename'bundle.js'// 创建的bundle的名称 
    }, 
    // 模块解析 
    module: { }, 
    // 插件 
    plugins: [ ]
 }

9.2 Webpack开发环境配置

接着给Webpack开发环境配置文件webpack.dev.js添加下面的相关配置:

const webpack = require('webpack'); 
const path = require('path'); 
const {merge} = require('webpack-merge');
const commonConfig = require('./webpack.common.js'); 
const DIST_PATH = path.resolve(__dirname, '../dist/'); 

module.exports = merge(commonConfig, { 
    mode'development'// 设置Webpack的mode模式 
    // 开发环境下需要的相关插件配置 
    plugins: [ ], 
    // 开发服务器 
    devServer: { 
        hottrue// 热更新,无需手动刷新 
        contentBase: DIST_PATH// host: '0.0.0.0', // host地址 
        port8080// 服务器端口 
        historyApiFallbacktrue// 该选项的作用所用404都连接到index.html 
        proxy: { 
            "/api""http://localhost:3000" // 代理到后端的服务地址,会拦截所有以api开头的请求地址 
            } 
        }
})

9.3 Webpack生产环境配置

继续给Webpack生产环境配置文件webpack.prod.js添加相关配置:

const webpack = require('webpack'); 
const path = require('path'); 
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js'); 
module.exports = merge(commonConfig, { 
    mode'production'// 设置Webpack的mode模式 
    // 生产环境下需要的相关插件配置 
    plugins: [ ], 
})

随着后续添加相应的配置信息,那么这三个文件中的配置信息会越来越多, 修改完Webpack的配置之后,对应的package.json中的scripts中的信息也要做相应的调整:

package.json 

// ... 其他配置信息请查看原文件 
"scripts": { 
    "build""webpack --config ./build/webpack.prod.js --mode production",
     "dev""webpack-dev-server --config ./build/webpack.dev.js --mode development --open""test""echo \"Error: no test specified\" && exit 1"
     }, 
}

这个时候重新在命令终端执行

// 执行build命令,重新打包  ⇒ npm run build  // 执行dev命令  ⇒ npm run dev

这仅仅是最基础部分的优化,因为我们的配置还是最简单的,后续我们添加了别的配置之后,也会在相应的步骤做相应的优化。

十. 编译 Webpack项目中的html类型的文件

这个plugin曝光率很高,他主要有两个作用

  • 为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题
  • 可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口

安装:yarn add html-webpack-plugin --dev

首先在项目根目录新建 index.html,作为模板文件进行打包 在插件里配置:

plugin是用于扩展webpack的功能,各种各样的plugin几乎可以让webpack做任何与构建先关的事情。 plugin的配置很简单,plugins配置项接收一个数组,数组里的每一项都是一个要使用的plugin的实例,plugin需要的参数通过构造函数传入。

var htmlWebpackPlugin = require('html-webpack-plugin');

plugins: [
    new htmlWebpackPlugin({
            filename: path.resolve(DIST_PATH,'index.html'), //打包后的文件名
            title'树鱼虚拟充值生态服务平台',  //打包后的页面title
            template: path.resolve(__dirname,'../index.html'),  //打包的模板文件
            injecttrue,
            hashtrue,
            favicon: path.resolve(__dirname, '../fav.ico')
        })
]

使用plugin的难点在于plugin本身的配置项,而不是如何在webpack中引入plugin,几乎所有webpack无法直接实现的功能,都能找到开源的plugin去解决,我们要做的就是去找更据自己的需要找出相应的plugin。

打包后title如何自动的添加到页面中呢?

只需要在模板文件的title中添加

<title><%= htmlWebpackPlugin.options.title %></title>

具体api配置说明如下:

var htmlWebpackPlugin = require('html-webpack-plugin');
new htmlWebpackPlugin({
            filename: , //输出的html的文件名称,默认是index.html
            title: '标题', //生成html文件的标题
            template: , //指定你生成的文件所依赖哪一个html文件模板,模板类型可以是html、jade、ejs等
            inject: true,  //true body head false
    //true 默认值,script标签位于html文件的 body 底部
	//body script标签位于html文件的 body 底部,(同true)
	//head script标签位于html文件的 head中
	//false 不插入生成的js文件,只是单纯的生成一个 html 文件,这个几乎不会用到的
            hash: true,
            favicon: path.resolve(__dirname, './fav.ico')  
            //给你生成的html文件生成一个 favicon ,值是一个路径
            //然后再生成的html中就有了一个 link 标签 
            //<link rel="shortcut icon" href="example.ico">
         minify:  //是否对HTML进行压缩,默认true压缩
        cache:true  //默认是true的,表示内容变化的时候生成一个新的文件
        showErrors://当webpack报错的时候,会把错误信息包裹再一个pre中,默认是true
        chunks: chunks, //chunks主要用于多入口文件,当你有多个入口文件,那就回编译后生成多个打包后的文件,那么chunks 就能选择你要使用那些js文件 ,
        excludeChunks://排除掉一些js 
        })

minify

minify 的作用是对 html 文件进行压缩,minify 的属性值是一个压缩选项或者 false 。默认值为false, 不对生成的 html 文件进行压缩。 下面罗列了一些常用的配置:

plugins:[
        new HtmlWebpackPlugin({
//部分省略,具体看minify的配置
minify: {
     //是否对大小写敏感,默认false
    caseSensitive: true,
    
    //是否简写boolean格式的属性如:disabled="disabled" 简写为disabled  默认false
    collapseBooleanAttributes: true,
    
    //是否去除空格,默认false
    collapseWhitespace: true,
    
    //是否压缩html里的css(使用clean-css进行的压缩) 默认值false;
    minifyCSS: true,
    
    //是否压缩html里的js(使用uglify-js进行的压缩)
    minifyJS: true,
    
    //Prevents the escaping of the values of attributes
    preventAttributesEscaping: true,
    
    //是否移除属性的引号 默认false
    removeAttributeQuotes: true,
    
    //是否移除注释 默认false
    removeComments: true,
    
    //从脚本和样式删除的注释 默认false
    removeCommentsFromCDATA: true,
    
    //是否删除空属性,默认false
    removeEmptyAttributes: true,
    
    //  若开启此项,生成的html中没有 body 和 head,html也未闭合
    removeOptionalTags: false, 
    
    //删除多余的属性
    removeRedundantAttributes: true, 
    
    //删除script的类型属性,在h5下面script的type默认值:text/javascript 默认值false
    removeScriptTypeAttributes: true,
    
    //删除style的类型属性, type="text/css" 同上
    removeStyleLinkTypeAttributes: true,
    
    //使用短的文档类型,默认false
    useShortDoctype: true,
    }
    }),
]

chunks

chunks主要用于多入口文件,当你有多个入口文件,那就回编译后生成多个打包后的文件,那么chunks 就能选择你要使用那些js文件

entry: {
    index: path.resolve(__dirname, './src/index.js'),
    devor: path.resolve(__dirname, './src/devor.js'),
    main: path.resolve(__dirname, './src/main.js')
}

plugins: [
    new httpWebpackPlugin({
        chunks: ['index','main']
    })
]

那么编译后:

<script type='text/javascript' src="index.js"></script>
<script type='text/javascript' src="main.js"></script>

而如果没有指定 chunks 选项,默认会全部引用。

excludeChunks

排除掉一些js,

entry: {
    index: path.resolve(__dirname, './src/index.js'),
    devor: path.resolve(__dirname, './src/devor.js'),
    main: path.resolve(__dirname, './src/main.js')
}

plugins: [
    new httpWebpackPlugin({
     excludeChunks: ['devor.js']
    })
]

那么编译后:

<script type=text/javascript src="index.js"></script>
<script type=text/javascript src="main.js"></script>

完整代码:

var pluginsAll2 = [];
var pages = glob.sync(path.join(htmlPagesPath,'**/*.html')); 
pages.forEach(function(page){
    var pagestr = page.match(/pages\/(\S*)\.html/);
    var name = pagestr[1];
    var plug = new htmlWebpackPlugin({
        filename:path.resolve(buildPath,name+'.html'),
        title:'测试',
        template:path.resolve(htmlPagesPath,name+'.html'),
        inject:true,
        chunks:[name],
        favicon: path.resolve(__dirname, './fav.ico') 
    })
    pluginsAll2.push(plug);
})

十一. loaders

常见的loader有:

file-loader,url-loader,style-loader,css-loader,less-loader,sass-loader,babel-loader,raw-loader,vue-loader webpack >= v2.0.0 开始默认支持json文件的导入

webpack是模块打包工具,⽽模块不仅是js,还可以是css,图片或者其他格式 但是webpack默认只知道如何处理js模块,那么其他格式的模块处理,和处理⽅式就需要loader

如何在webpack中配置第三方loader?

配置第三方loader,需要在webpack的配置文件中新增一个module节点,节点中是一个一个的规则集合,集合名字是rules,需要添加loader就在rules的集合中新增一个规则;每个规则必须的两个配置:

(1)test  :test搭配的是键值对,值是一个正则表达式,用来匹配要处理的文件类型; (2)use  :用来指定使用哪个loader模块来打包处理该文件;

module:{  
    rules:[  
        {    
        test:/\.xxx$/,    
        use:{      
            loader: 'xxx-load'    
        }  
        } 
     ] 
}

当webpack处理到不认识的模块时,需要在webpack中的module处进行配置,当检测到是什么格式的 模块,使⽤用什么loader来处理

11.1 file-loader:处理静态资源模块

1、作用:file-loader可以用来帮助webpack打包处理一系列的图片文件;比如:.png 、 .jpg 、.jepg等格式的图片;

2、使用file-loader打包图片的结果:使用file-loader打包的图片会给每张图片都生成一个随机的hash值作为图片的名字;

loader: file-loader 原理理是把打包入口中识别出的资源模块,移动到输出目录,并且返回⼀个地址名称

所以我们什么时候用file-loader呢? 场景:就是当我们需要模块,仅仅是从源代码挪移到打包⽬目录,就可以使⽤用file-loader来处理理, txt,svg,csv,excel,图片资源等

yarn add file-loader -D 或者 npm install file-loader -D

案例:


module: {    
    rules: [      
        {        
            test: /\.(png|jpe?g|gif)$/,        
            //use使用一个loader可以用对象,字符串,两个loader需要用数组        
            use: {          
                loader: "file-loader",          
                // options额外的配置,⽐比如资源名称          
                options: {            
                        // placeholder 占位符  [name]老资源模块的名称            
                        // [ext]老资源模块的后缀            
                        // https://webpack.js.org/loaders/file-loader#placeholders            
                        name: "[name]_[hash].[ext]",            
                        //打包后的存放位置            
                        outputPath: "images/"  	        
                    }        
            }      
        }    
    ]  
},

11.2 url-loader :处理图片base64

当图片较小的时候会把图片转换成base64编码,大于limit参数的时候还是使用file-loader进行拷贝。这样做是好处是可以直接将图片打包到bundle.js里,不用额外请求图片(省去HTTP请求)

作用: 可以弥补处理file-loader不能生成base64图片格式的缺陷,对小体积的图片比较合适,大图⽚不合适。但是,也是需要安装file-loader

安装 : yarn add url-loader -D 或者 npm install url-loader -D

案例:

module: {    
    rules: [      
        {        
            test: /\.(png|jpe?g|gif)$/,   //是匹配图片文件后缀名称     
            use: {          
                loader: "url-loader",    //是指定使用的loader和loader的配置参数      
                options: {            
                    name: "[name]_[hash].[ext]",            
                    outputPath: "images/",  //图片打包到的文件目录          
                    //小于2048B,才转换成base64 的文件打成Base64的格式,写入JS           
                    limit: 2048 ,
                    publicPath:’/img’  //最终生成的CSS代码中,图片URL前缀       
                }        
            }      
        }    
    ]  
},

11.3 CSS预处理

(1) CSS loader配置

如果没有安装style-loader css-loader直接引入css文件就会报错:

这是因为在Webpack中没有CSS相关的Loader配置。那么接下来,来解决CSS Loader在Webpack中的配置:

  • css-loader使你能够使用类似@import和url()的方法实现require()的功能
  • style-loader将所有的计算后的样式加入页面中
  • 而vue-style-loader是vue官方基于style-loader开发的适用于vue的样式解析,sass-loader用来解析sass/scss文件

两者结合在一起能够把样式嵌入Webpack打包后的JavaScript文件中

安装:yarn add style-loader css-loader --dev
module:{
    rules:[
        { 
            test: /\.css$/, 
            exclude: /node_modules/,
            use: [{ loader: "style-loader"}, { loader: "css-loader" } ]   //方式一
            //use: ["style-loader", loader: "css-loader" ]  //方式二
        }
    ]
}

(2) 配置less环境需要安装:

安装:yarn add less less-loader --dev
module:{
    rules:[
        { 
            test: /\.css$/, 
            exclude: /node_modules/,
            use: [{ loader: "style-loader"}, { loader: "css-loader" } ]   //方式一
            //use: ["style-loader", loader: "css-loader" ]  //方式二
        },
        { 
        test: /\.less$/, 
        use: [
              //方式一
            { loader: "style-loader" }, 
            { loader: "css-loader" }, 
            { loader: "less-loader" }]  
           //方式二
        // use: ['style-loader','css-loader','less-loader']  
        }
    ]
}

(3) 配置scss环境需要安装:

sass-loader 把sass语法转换成css ,依赖node-sass模块

安装:yarn add node-sass sass-loader --dev

//注意:在项目中要less预处理和sass预处理二者选其一,不要同时混着用

loader有顺序,从右到左,从下到上

module:{
    rules:[
        { 
        test: /\.scss$/, 
        //方式一
        use: [
            { loader: "style-loader" }, 
            { loader: "css-loader" },
             { loader: "sass-loader" }]  
        //方式二
        // use: ['style-loader','css-loader','sass-loader']   
        }
    ]
}

(4) 添加PostCSS相关配置

PostCSS是一个很优秀的东西,他有很多优秀的插件,比如postcss-preset-env、Autoprefixer等。另外自己还可以根据自己需要扩展自己想要的PostCSS插件。而且在一些移动端的布局中的方案都会需要处理CSS单位之间的转换,比如Flexible(px2rem),vw-layout(px2vw)等。

可以说,现代Web开发中PostCSS相关的配置是工程体系中必不可少的。接下来,我们就看看如何在该工程体系中添加PostCSS相关的配置。要配置PostCSS相关的事项,需要:

  • 安装postcss、postcss-loader
  • 在Webpack配置中添加PostCSS相关的配置
  • 安装自己需要的PostCSS插件
  • 在postcss.config.js或.postcssrc.js添加有关于PostCSS的插件配置

先来执行第一步,安装postcss和postcss-loader:

安装:yarn add postcss-loader  postcss --dev
module:{
    rules:[
        { 
            test: /\.css$/, 
            exclude: /node_modules/,
            //use: [{ loader: "style-loader"}, { loader: "css-loader" } ]  
            //use: ["style-loader", loader: "css-loader" ]

            use: [
                { loader: "style-loader"}, 
                { loader: "css-loader" },
                {loader: 'postcss-loader'}
            ]  
        }
    ]
}

如果直接启动会报错:No PostCSS Config found....

接下来需要给项目添加PostCSS配置相关的信息。先在项目的根目录下创建postcss.config.js或.postcssrc.js文件,并添加相关的配置:

// postcss.config.js


module.exports = { 
plugins: { }, 
}

上面的配置还没有添加任何PostCSS相关的插件。为了验证我们的配置是否成功,来安装两个PostCSS的插件,

- 添加css3前缀

postcss-preset-env 将现代CSS转换成浏览器能理解的东西

安装postcss-preset-env,无需再安装autoprefixer,由于postcss-preset-env已经内置了相关功能。

安装:yarn add postcss-preset-env --dev

并把这两插件的相关配置中添加到postcss.config.js中:

module.exports = {
        plugins: {
            'postcss-preset-env': {
                autoprefixer: { flexbox: 'no-2009'},  //可去掉
                stage: 3
            },
          }
      }

如果安装了自动补全插件 autoprefixer, yarn add autoprefixer --dev 也可以通过这个配置来生效

module.exports = {
        plugins: {
            'autoprefixer': { }
          }
      }

在样式文件中添加一些样式代码来验证是否生效了:

.box {
        width50px;
        height30px;
        backgroundurl(../images/ABC.jpg);
        transform:rotate(30deg);
    }

如果生效,会是这样的

.box
{
transform:rotate(7deg);
-ms-transform:rotate(7deg); 	/* IE 9 */
-moz-transform:rotate(7deg); 	/* Firefox */
-webkit-transform:rotate(7deg); /* Safari 和 Chrome */
-o-transform:rotate(7deg); 	/* Opera */
}

如果没有说明没有生效, 需要配置

需要在package.json中添加browserslist相关的配置信息:

package.json

  "browserslist": [
    "last 10 Chrome versions",
    "last 5 Firefox versions",
    "Safari >= 6",
    "ie> 8"
  ],
- 单位转换 px - rem

把项目中px单位转化为 rem,实现响应式,

安装:
yarn add postcss-pxtorem -D  
module.exports = { 
            plugins:{
                'postcss-preset-env': { 
                    autoprefixer:{ 
                        flexbox'no-2009'
                        }, 
                    stage3
                },
                'postcss-pxtorem':{
                    rootValue:10,
                    minPixelValue:2,
                    propWhiteList: []
                }
              }
          }
(5) css抽离 后面插件讲

11.4 使用ejs模块化导入

11.4.1 安装:

yarn add ejs ejs-loader

11.4.2 配置:

{
    test:/\.ejs$/,
    use:{ 
        loader:'ejs-loader',
        options:{
            esModule:false
        }
    }
},

注意: options:{ esModule:false }必须要加,不然会报,让ejs模块导入不支持esModule,支持commonJS的语法

然后再templates目录下 mkdir modules & cd modules & touch header.ejs

并添加相关代码

再在模板文件 index.ejs里 通过 下列语法导入

<%= require(./modules/header.ejs)() %>

十二、增加babel⽀持

12.1 Babel 是什么?

Babel 是一个 JavaScript 编译器

Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情:

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)
  • 源码转换 (codemods)

@babel/preset-env + @babel/polyfill 可以完全实现 ES 基础语法的转译 + 新 API 的转译,这是配置 Babel 转码的第一种方式。

12.1 babel7 的一些变化

preset 的变更: 淘汰 es201x,删除 stage-x,推荐 env

如果你还在使用 es201x,官方建议使用 env 进行替换。淘汰并不是删除,只是不推荐使用。 但 stage-x 是直接被删了,也就是说在 babel7 中使用 es201X 是会报错的。

包名称变化

babel 7 的一个重大变化,把所有 babel-* 重命名为 @babel/*,

例如:
babel-cli —> @babel/cli。
babel-preset-env —> @babel/preset-env

Babel附带了一个内置的CLI(@babel/cli),可用于从命令行编译文件.

你可以在您的机器上全局安装@babel/cli,但最好进行局部安装。

  • 同一台机器上的不同项目可以依赖于不同版本的Babel,允许您单独更新它们
  • 没有对工作环境的隐式依赖性会使项目更易于移植和设置
yarn add @babel/core @babel/cli -D 

12.2 安装

安装:yarn add @babel/core  babel-loader @babel/preset-env -D

其中preset是babel的配置,babel-loader在webpack中解析babel中会用到,polyfill也是babel的一个依赖在webpack中解析babel中会用到

12.3 babel-loader

babel-loader是webpack 与 babel的通信桥梁,在webpack中解析babel中会用到,不会做把es6转成es5的工作,这部分工作需要用到 @babel/preset-env来做

@babel/preset-env里包含了es6转es5的转换规则

babel-preset-es2015 已经被babel-preset-env替代,现在已经是@babel/preset-env

@babel/preset-env 这个比较强大, 是一个面向未来得库, 即使是es7,8,9出来以后,都可以帮忙处理

12.4 Babel7 中 @babel/preset-env 的使用

是一系列插件的集合,包含了我们在babel6中常用的es2015,es2016, es2017等最新的语法转化插件,允许我们使用最新的js语法,比如 let,const,箭头函数等等,但不包括stage-x阶段的插件。

preset 即“预制套件”,包含了各种可能用到的转译工具

基本的语法转换,需要添加预设 @babel/preset-env

安装依赖包

yarn add babel-loader  @babel/core @babel/preset-env -D

配置如下:

{ 
test:/\.(jsx|js)$/, 
use:{ 
    loader:'babel-loader', 
}, 
exclude:/node_modules/ 
} 

.babelrc 配置:

{ 
    "presets":["@babel/preset-env",{
        "targets": {  } 
    }] 
} 

注意:浏览器兼容的配置

12.5 browserslist的作用

我们需要知道的是制定个targets的browsers时使用的是 browserslist ,有3个地方可以进行配置:

  • .babelrc 文件 的targets
  • package.json文件的browserslist
  • .browserslistrc

我们可以在 .babelrc 文件、package.json文件、browserslist中指定浏览器版本选项,优先级规则是 .babelrc文件定义了则会忽略 browserslist、.babelrc 没有定义则会搜索 browserslist 和 package.json 两者应该只定义一个,否则会报错。

至于要兼容哪些浏览器,可以自行配置,这里可以提供2个配置

12.5.1 使用方法

(1) .babelrc

根据浏览器的市场份额来定

{ 
    "presets":["@babel/preset-env",{
        "targets": { 
                   "browsers": [ "> 1%""last 5 versions""ie >= 8" ] 
          } 
    }] 
} 

(2) package.json (推荐)

在package.json文件添加browserslist

 "browserslist": [
    last 5 version
    "ie>= 8"
  ],

(3) .browserslistrc 在项目根目录新建 .browserslistrc ,添加以下兼容哪些浏览器的代码

# Browsers that we support 
 
last 5 version
> 1%
ie >= 8

如果你想知道配置语句的查询结果可以使用[online demo] (browserl.ist/) 你就可以看到最新的各个浏览器版本

12.5.2 支持的插件

Browserslist这个东西单独是没用的,(补充: 在vue官方脚手架中,browserslist字段会被 @babel/preset-env 和 Autoprefixer 用来确定需要转译的 JavaScript 特性和需要添加的 CSS 浏览器前缀)下面的搭配的工具列表:

  • Autoprefixer
  • Babel
  • postcss-preset-env
  • eslint-plugin-compat
  • stylelint-no-unsupported-browser-features
  • postcss-normalize

或者npx browserslist

Browserslist 默认值: 
> 0.5%, 
last 2 versions, 
Firefox ESR, 
not dead

12.5.3 最佳实践

最好不要这么写

 "last 10 Chrome versions",
 "last 5 Firefox versions",
"Safari >= 6",

因为有很多浏览器厂商 , 不能一一全写, 从市场份额来分就行

  • last 2 Chrome versions 这种配置最好当你只想在对应的一个浏览器下开发webapp,生产情况下最好不要,毕竟其他浏览器也有市场份额
  • 如果你想自己配置,我建议你结合 last 1 version, not dead with > 0.2% (or > 1% in US, > 1% in my stats. last n version会增加很过过时的浏览器并且没有增加流行的旧版本浏览器。> 0.2%看起来更加合适(根据份额来定)
  • 不要仅因为您不了解浏览器而删除浏览器。Opera Mini在非洲拥有1亿用户,它在全球市场上比微软边缘更受欢迎。中文QQ浏览器的市场份额比Firefox和桌面Safari都要多。(第一次知道,qq还是强啊,但是360呢..)

测试代码:

//index.js 
const arr = [new Promise(() => {}), new Promise(() => {})];
arr.map(item => {  console.log(item); });

var isCol = ['11','22'].includes('11');
console.log(isCol);

在ie10会报错

通过上面的几步 还不够,includes Promise等一些还没有转换过来,这时候需要借助@babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性

12.5 @babel/polyfill (1)

也就是babel6 中的 babel-polyfill , 使用 preset-env 能将最新的语法转换为ecmascript5的写法,当我们需要使用新增的全局函数(比如promise, Array.from)和实例方法(比如 Array.prototype.includes )时就需要引入 polyfill ,一般用法 在 index.js文件的最上层引入,或是在打包文件的entry 入口处引入,这样做的缺点是会全局引入整个 polyfill包,比如promise 会全局引入,污染全局环境。

以全局变量的方式注入进来的。windows.Promise,它会造成全局对象的污染

安装: yarn add @babel/polyfill  --dev

Webpack.config.js配置:

{    
test: /\.js$/,    
exclude: /node_modules/,    
loader: "babel-loader",    
options: {        
    presets: ["@babel/preset-env"]   
 } 
 }

index.js 顶部引入

import "@babel/polyfill";

会发现打包的体积大了很多,这是因为polyfill默认会把所有特性注入进来,假如我想我用到的es6+,才会注入,没用到的不注入,从而减少打包的体积,可不可以呢

当然可以

修改Webpack.config.js

{ 
    test:/\.(jsx|js)$/, 
    use:{ 
    loader:'babel-loader',  //webpack 与 babel的通信桥梁
    // options:{ 
    //         presets:[ "@babel/preset-env"]  // 把es6转成es5的工作
    //     } 
    // }, 
    exclude:/node_modules/ 
}

babelrc文件: 项目根目录, 新建 .babelrc文件

{ 
    "presets": [
        [ 
            // 把es6转成es5的工作
           "@babel/preset-env", { 
               "useBuiltIns": "usage", //按需注入
               "targets": { 
                   "browsers": [ "> 1%""last 5 versions""ie >= 8" ] 
               } 
           }
        ]
   ]
} 

进一步测试,这个时候ie9是可以运行, 但是IE8 提示

原因:在IE8下只能对DOM对象使用, 如果对原生对象使用Object.defineProtry()会报错 参考:www.cnblogs.com/zhangrunhao…

12.6 @babel/polyfill 和 @babel/preset-env 的关系

@babel/preset-env 中与 @babel/polyfill 的相关参数有 targets 和 useBuiltIns 两个

  • targets: 支持的目标浏览器的列表

  • useBuiltIns: 选项是 babel 7 的新功能,这个选项告诉 babel 如何配置 @babel/polyfill , 它有三个参数可以使用:

  • entry: 需要在 webpack 的入口文件里 import "@babel/polyfill" 一次。

  • usage: 不需要 import "@babel/polyfill" ,全自动检测,但是要安装 @babel/polyfill 。

  • false: 这也是默认值 , 使用这个值时不引入 polyfill

使用webpack,有多种方法可以包含polyfill

  • 当与babel-preset-env一起使用时,如果在.babelrc中指定了useBuiltIns: 'usage',那么在webpack.config.js配置的entry处或源代码中都不要包含babel-polyfill。注意,babel-polyfill仍然需要安装。

  • 如果在.babelrc中指定了useBuiltIns: 'entry',那么在应用程序入口点的顶部包括babel-polyfill,方法是按照上面讨论的require或import。

  • 如果在.babelrc中未指定useBuiltIns键,或者使用useBuiltIns: false设置了该键,则直接将babel-polyfill添加到webpack.config.js中的entry中。

{ 
    "presets": [
     [ 
        "@babel/preset-env", { 
            "useBuiltIns""usage",  //entry,false
            "targets": { 
                "browsers": [ "> 1%""last 5 versions""ie >= 8" ] 
            } 
        }
     ]
    ]
  }

在引入@babel/preset-env(一个帮你配置babel的preset,根据配置的目标环境自动采用需要的babel插件)配置babel中的useBuiltIns: 'usage'时,编译出现又出现这个警告提示

问题解释:警告在使用useBuiltIns选项时,需要声明core-js的版本

解决办法: 1)方法一:安装yarn add core-js@2 2)方法二:在 .babelrc 文件中添加corjs:”2”,申明corejs的版本

{ 
    "presets": [
     [ 
        "@babel/preset-env", { 
            "useBuiltIns""usage",
            "corejs""2"// 声明corejs版本
            "targets": { 
                "browsers": [ "> 1%""last 5 versions""ie >= 8" ] 
            } 
        }
     ]
    ]
  }

core-js 支持两个版本, 2 或 3, 很多新特性已经不会加入到 2 里面了, 比如: flat 等等最新的方法, 2 这个版本里面都是没有的, 所以建议大家用3

完美解决问题!

总结:在业务项目中需要用到polyfill时, 可以使用和 @babel/preset-env 的 targets 和 useBuiltIns: usage来根据目标浏览器的支持情况,按需引入用到的 polyfill 文件。

12.6 @babel/runtime(2)

这种方式会借助 helper function 来实现特性的兼容, 并且利用 @babel/plugin-transform-runtime 插件还能以沙箱垫片的方式防止污染全局, 并抽离公共的 helper function , 以节省代码的冗余

也就是说 @babel/runtime 是一个核心, 一种实现方式, 而 @babel/plugin-transform-runtime 就是一个管家, 负责更好的重复使用 @babel/runtime

@babel/plugin-transform-runtime 插件也有一个 corejs 参数需要填写

版本2 不支持内置对象 , 但自从Babel 7.4.0 之后,拥有了 @babel/runtime-corejs3 , 我们可以放心使用 corejs: 3 对实例方法做支持

当我们开发的是组件库,工具库这些场景的时候,polyfill就不适合了,因为polyfill是注入到全局变量,window下 的,会污染全局环境,所以推荐闭包方式: @babel/plugin-transform-runtime 参考:www.cnblogs.com/htoooth/p/9…

它不会造成全局污染

yarn add @babel/plugin-transform-runtime --dev
yarn add @babel/runtime --save   //注意这里安装到dependences的依赖包

如果报错,还需要安装 @babel/runtime-corejs3

yarn add @babel/runtime-corejs3 -D

12.6.1. 怎么使用?

先注释掉index.js里的polyfill

修改配置文件:注释掉之前的presets,添加plugins

options: {  
    "presets": [
        [ 
           "@babel/preset-env", { 
              //  "useBuiltIns": "usage", //按需注入
              // "corejs": "2", // 声明corejs版本
               "targets": { 
                   "browsers": [ "> 1%""last 5 versions""ie >= 8" ] 
               }  
           }
        ]
       ],
    "plugins": [   
        [      
        "@babel/plugin-transform-runtime",     
            {

                "absoluteRuntime": false,        
                "corejs": 3,        
                "helpers": true,        
                "regenerator": true,       
                "useESModules": false     
            }    
        ]  
    ] 
}

我们看到使用 @babel/plugin-transform-runtime 编译后的代码和之前的 @babel/preset-env 编译结果大不一样了, 它使用了帮助函数, 并且赋予了别名 , 抽出为公共方法, 实现复用。 比如它用了 _Promise 代替了 new Promise , 从而避免了创建全局对象

12.7 上面两种处理pollyfill方式一起用会怎么样

12.7.1 useage 和 @babel/runtime

useage 和 @babel/runtime 同时使用的情况下比较智能, 并没有引入重复的 polyfill

12.7.2 entry 和 @babel/runtime

跟 useage 的情况不一样, entry 模式下, 在经过 @babel/runtime 处理后不但有了各种帮助函数还引入了许多polyfill, 这就会导致打包体积无情的增大

个人分析: entry 模式下遭遇到入口的 import "core-js" 及就立即替换为当前目标浏览器下所需的所有 polyfill, 所以也就跟 @babel/runtime 互不冲突了, 导致了重复引入代码的问题, 所以这两种方式千万不要一起使用, 二选一即可

方式二: 下面的2种情况都不推荐

// 情况一:在webpack中引用
module.exports = {
  entry: ["babel-polyfill""./app/js"]   //不推荐 
};
 
// 情况二:在js入口顶部引入
import "babel-polyfill";  //不推荐
https://babeljs.io/docs/en/6.26.3/babel-polyfill

12.7.3 总结:

  1. @babel/preset-env 拥有根据 useBuiltIns 参数的多种polyfill实现,优点是覆盖面比较全(entry), 缺点是会污染全局, 推荐在业务项目中使用
  • entry 的覆盖面积全, 但是打包体积自然就大,
  • useage 可以按需引入 polyfill, 打包体积就小, 但如果打包忽略node_modules 时如果第三方包未转译则会出现兼容问题
  1. @babel/runtime 在 babel 7.4 之后大放异彩, 利用 corejs 3 也实现了各种内置对象的支持, 并且依靠 @babel/plugin-transform-runtime 的能力,沙箱垫片和代码复用, 避免帮助函数重复 inject 过多的问题, 该方式的优点是不会污染全局, 适合在类库开发中使用 上面 1, 2 两种方式取其一即可, 同时使用没有意义, 还可能造成重复的 polyfill 文件

十三、常见插件Plugins

13.1 重点插件plugin介绍

插件与模块解析的功能不一样,模块解析是为了导入非es5格式js或其它资源类型文件,定制了一些loader。插件是对最后的打包文件进行处理的,也可以理解loader是为打包前做准备,plugin是打包再进行处理

官方插件的使用步骤(内置插件2步)

① 配置文件中导入XxxxPlugin, const wp= require(‘webpack’)

② 在plugins这个数组中加入一个插件实例,new wp.XxxxPlugin({对象})

第三方插件的使用步骤(第3方3步,多一次安装)

① 安装(第三方插件要安装) 根目录>npm i -D XxxxPlugin

② 配置文件中导入插件 const XxxxPlugin = require('xxxx-plugin')

③ 在plugins这个数组中加入一个插件实例,new XxxxPlugin({对象})

官方插件有

可以在配置中打印查看

const webpack = require('webpack')
console.log(webpack ) //这里可以看到哪些是webpack内置的插件

待讲插件清单

01: webpack.BannerPlugin 加注释 02: terser-webpack-plugin 代码缩小 03: html-webpack-plugin 生成html页 04: 以前的extract-text-webpack-plugin,mini-css-extract-plugin 提取css等 05.DefinePlugin //定义一个全局常量,如new wp.DefinePlugin({BJ: JSON.stringify('北京'),}),在待打包的js文件中可以直接使用,如在./src/main.js中console.log('我是在配置文件中定义的'+BJ) 06.Dllplugins

13.2 BannerPlugin

作用: 在打包的文件中添加注释 //这是webpack内置的插件,所以不用require导入,但是对于第三方插件要先导入

plugins: [
        new webpack.BannerPlugin({      
banner: '永远要记得,成功的决心远胜于任何东西'
        })

    ]
    

13.3 打包复制

作用:在webpack中拷贝文件和文件夹

安装: yarn add copy-webpack-plugin --dev
from  定义要拷贝的源文件            from:__dirname+'/src/components'
to      定义要拷贝到的目标文件夹     to: __dirname+'/dist'
toType  file 或者 dir               可选,默认是文件
force   强制覆盖前面的插件           可选,默认是文件
context                              可选,默认base   context可用specific  context
flatten  只拷贝指定的文件            可以用模糊匹配
ignore  忽略拷贝指定的文件           可以模糊匹配

插件引入和配置:

const CopyWebpackPlugin = require('copy-webpack-plugin');
new CopyWebpackPlugin({
  patterns:[
    {
      from:path.resolve(__dirname,'../static'),
      to:dist_path+'/static'
    }
  ]
})

13.4 js压缩插件

作用:压缩js 代码,减小体积 这个插件不是内置的,要先安装

不推荐使用 webpack-parallel-uglify-plugin 项目基础处于没人保护的阶段,issue 没人处置,pr没人兼并。

推荐运用 terser-webpack-plugin

terser-webpack-plugin 是一个运用 terser 紧缩js的webpack 插件。 紧缩是最耗时的一个步骤,如果是你是在webpack 4 中,只需几行代码,即可加快你的构建速率。

安装:因为是第三方的 , 所以需要安装 , github.com/webpack-con…

yarn add terser-webpack-plugin -D 

用法

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: 4,   //多线程
      }),
    ],
  },
};

13.5 抽离css样式

抽离css的目的是防止将样式打包在js中方便缓存静态资源

插件一: extract-text-webpack-plugin(不推荐) yarn add extract-text-webpack-plugin^4.0.0-beta.0 --dev 注意:如果用这个 需要 指定版本:"extract-text-webpack-plugin": "^4.0.0-beta.0"

插件二:mini-css-extract-plugin(推荐)

13.5.1 extract-text-webpack-plugin 演示:

module配置:

const ExtractTextPlugin = require("extract-text-webpack-plugin");
//module部分
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: "css-loader"
        })
      }
    ]
  },

plugins插件部分

  plugins: [
    new ExtractTextPlugin("styles.css"),
  ]
}

use:指需要什么样的loader去编译文件,这里由于源文件是.css所以选择css-loader fallback:编译后用什么loader来提取css文件

13.5.2 mini-css-extract-plugin 演示:

相比extract-text-webpack-plugin:

  • 异步加载
  • 没有重复的编译(性能)
  • 更容易使用
  • 特定于CSS

安装: yarn add mini-css-extract-plugin -D

抽出 css :

module部分

const MiniCssExtractPlugin= require("mini-css-extract-plugin");
//........
 { 
            test: /\.less$/, 
            exclude: /node_modules/,
            use: [
              {
                  loader:MiniCssExtractPlugin.loader //代替style-loader
              },
              // { loader: "style-loader"}, 

              { loader: "css-loader" }, 
              { loader: 'postcss-loader'},
              { loader: "less-loader" }
         ]  
 },

plugins插件部分

// 这里的配置和webpackOptions.output中的配置相似 // 即可以通过在名字前加路径,来决定打包后的文件存在的路径

new MiniCssExtractPlugin({
  filename:'styles/[name].[contenthash:8].css',
  chunkFilename:'style/[id].[contenthash:8].css'  //按需引入生成css
})
 

[id]和[name]在webpack中被称做placeholder 用来在webpack构建之后动态得替换内容的(本质上是正则替换)。 chunkFilename是构建应用的时候生成的。

13.6 压缩css: optimize-css-assets-webpack-plugin

  • 生产环境的配置,默认开启tree-shaking和js代码压缩;
  • 通过optimize-css-assets-webpack-plugin插件可以对css进行压缩,与此同时,必须指定js压缩插件(例子中使用terser-webpack-plugin插件),否则webpack不再对js文件进行压缩; 设置optimization.splitChunks.cacheGroups,可以将css代码块提取到单独的文件中。

安装:yarn add optimize-css-assets-webpack-plugin -D

引入插件与配置:

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = merge(common, {
  mode: 'production', 
      optimization:{
        minimize:true,
        minimizer:[
            new TerserPlugin({
                cache:true, //释放要缓存
                parallel: 4,   //多线程,并行打包
              }),
              new OptimizeCSSAssetsPlugin({})
        ]
    },
  })
  

总结

  • 不同环境下的打包,如果出现图片显示不了时(特别是css中的图片),请检查publicPath的配置。

  • mode: 'production'会开启tree-shaking和js代码压缩,但配置optimization. minimizer会使默认的压缩功能失效。所以,指定css压缩插件的同时,务必指定js的压缩插件。

  • mini-css-extract-plugin插件,可以把css代码打包到单独的css文件,且可以设置存放路径(通过设置插件的filename和chunkFilename)。

splitChunks就算你什么配置都不做它也是生效的,源于webpack有一个默认配置,这也符合webpack4的开箱即用的特性,它的默认配置如

13.7 生成json文件的列表索引插件

安装: yarn add assets-webpack-plugin --dev

var AssetsPlugin = require('assets-webpack-plugin');
//生成json文件的列表索引插件
new AssetsPlugin({
    filename: 'assets-resources.json',
    fullPath: false,
    includeManifest: 'manifest',
    prettyPrint: true,
    update: true,
    path: buildPath,
    metadata: {version: 123}
});

13.8 sourceMap

选择一种 source map 格式来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。

www.webpackjs.com/configurati…

在dev模式中,默认开启,关闭的话 可以在配置文件⾥ devtool:"none"

开发环境推荐 devtool:"cheap-module-eval-source-map"

线上环境可以不开启:如果要看到一些错误信息,推荐; devtool:"cheap-module-source-map"

十四、 置别名快捷方式 resolve

 resolve: {
        extensions: ['.js''.vue''.json'],
        alias: {
          'src': srcPath,
          'styles': srcPath+'/styles',
          'images':srcPath+'/images',
          'config':path.resolve(srcPath,'js/config.js')
        }
      }

resolve.alias :设置别名 resolve.enforceExtension :默认是false 如果是 true,将不允许无扩展名(extension-less)文件。如果启用此选项,只有 require('./foo.js') 能够正常工作。 resolve.extensions 自动解析确定的扩展。

十五、 设置环境变量 通过script

很久以前这样用过:

script:{
"start": "export NODE_ENV='development' && node app.js" 
// 在Mac和Linux上使用export, 在windows上export要换成set
}

目前推荐 cross-env

15.1 cross-env

安装:yarn add cross-env --dev 

注意:cross-env scene=dev cross-env scene=prod 后面不能添加&

"scripts": { 
 "dev": "cross-env scene=dev webpack-dev-server --config webpack.config.js", 
 "build": "cross-env scene=prod webpack --mode production --config 
webpack.config.js" 
 },

如果有打包之前删除 打包的功能,如下,注意顺序:

  "scripts": {
    "test": " rm -rf dist & cross-env scene=test webpack --config ./build/webpack.prod.js ",
    "prod": " rimraf dist && cross-env scene=prod webpack --config ./build/webpack.prod.js ",
    "dev": "cross-env scene=dev webpack-dev-server --config ./build/webpack.dev.js ",
    "dev1": "webpack-dev-server --config test.js --mode development",
    "delete": "rimraf dist1"
  },

需求: 根据不同的开发环境,来设置一些变量

console.log(process.env.scene);
var isProduction = (process.env.scene=='prod');

十六、 DefinePlugin 设置 全局常量

DefinePlugin 允许创建一个在编译时可以配置的全局常量 在配置文件里获取变量并把值设置,DefinePlugin是webpack自带的插件,无法安装

new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
  'process.env.DEBUG': JSON.stringify(process.env.DEBUG)
})
pluginsAll.push(new webpack.DefinePlugin({
    'sceneParam'JSON.stringify(process.env.scene),
    'laney':JSON.stringify('laney'),
    'test':'"kkkkk"'
})); 

然后在项目中,根据不同的环境来调用不同的样式 ,异步加载模块的方式有2种, es6的模式,如下

switch(sceneParam){
        case 'prod':
          console.log('11111'); 
          break;
        case 'dev':
             console.log('222222'); 
          break;
 }

如果这么使用会报错:  import 和 export 只能在顶级用,不能在代码块中用。否则会报 'import' and 'export' may only appear at the top level。

 使用commonJS异步的模块加载

 这个算是可以减小模块的体积吧,在一定程度上也是为用户考虑的,使用require.ensure来设置哪些模块需要异步加载,webpack会将它打包到一个独立的chunk中, 在某个时刻(比如用户点击了查看)才异步地加载这个模块来执行

$('.bg-input').click(() => {
    console.log('clicked, loading async.js')
    require.ensure([], require => {
        require('./components/async2').log();
        require('./components/async1').log();
        console.log('loading async.js done');
    });
});

十七、 EnvironmentPlugin

EnvironmentPlugin 是一个通过 DefinePlugin 来设置 process.env 环境变量的快捷方式。 用法: new webpack.EnvironmentPlugin(['NODE_ENV', 'DEBUG']) 不同于 DefinePlugin,默认值将被 EnvironmentPlugin 执行 JSON.stringify。 上面的写法和下面这样使用 DefinePlugin 的效果相同:


new webpack.EnvironmentPlugin({
  NODE_ENV: 'development', 
// 除非有定义 process.env.NODE_ENV,否则就使用 'development'
  DEBUG: false})
  

在页面中访问的方式:

if (process.env.NODE_ENV === 'production') {
  console.log('Welcome to production');
}
if (process.env.DEBUG) {
  console.log('Debugging output');
}

十八、 配置全局可引用的配置文件 webpack.ProvidePlugin

在src/js里添加一个 配置文件,或者全局文件configCommon,然后在build/webpack.common.js 的插件里修改如下:

plugins.push(new webpack.ProvidePlugin({
    // config:path.resolve(srcPath,'js/config.js')  ////导出的方式为export { }
    // config:'config'
    // config:['config','default'],
    configCommon:[path.resolve(srcPath,'js/config.js'),'default'],  ////导出的方式为export default{ }
    // $:'jquery',
    // jQuery:'jquery'
  }));

重启项目后,在页面中就可以在项目文件中直接用 configCommon,包括里面的方法和 各种参数,非常爽.

18.1 使用场景微服务架构

方式一:webpack.ProvidePlugin 为了保证一个服务坍塌了, 不影响别的服务,需要有多个服务,多个域名, 如果是项目中可能有多个接口的域名, 不同的环境, 这些 接口的 域名都不一样, 这个时候 ,需要根据 开发 环境, 测试环境,以及线上分别配置,比如:

commonFun.environment = {
    // 开发环境:
    dev:{
        service:'http://localhost:8081',
        setting:'http://localhost:8082',
        manage:'http://localhost:8083'
    },
    // 测试环境:
    test:{
        service:'http://192.168.0.1:8081',
        setting:'http://192.168.0.1:8082',
        manage:'http://192.168.0.1:8083'
    },
    // 线上环境:
    prod:{
        service:'http://test1.ruanmou.com',
        setting:'http://test2.ruanmou.com',
        manage:'http://test3.ruanmou.com'
    }
}

如何去根据不同的环境调用这些 api的域呢, 第一种方法可以用webpack.ProvidePlugin ,在src\js\config.js加上以上配置,在其他页面就可以直接通过这个configCommon.environment[sceneParam] 获取到当前所有接口需要的域

18.2 方式二:在根目录添加配置,根据环境写入到index.html

但是这个 方法 是每次都需要重新打包才可以生效, 有没有有一种方法可以 修改配置后, 不需要打包的呢, 那这个时候可以 把配置文件在 index.html里直接通过script引入相应环境的配置。 在根目录添加 service.local.js ,用于本地开发 配置如下:

window.serviceIpConfig = {
  dev:{
    Product:'http://10.0.1.28:888',//商品服务
    Order:'http://10.0.1.28:888',//订单服务
    Member:'http://10.0.1.28:999',//会员服务
    Auth:'http://10.0.1.28:222',//授权服务
  },
  debug:{
    Product:'http://127.0.0.1:888',//商品服务
    Order:'http://127.0.0.1:888',//订单服务
    Member:'http://127.0.0.1:999',//会员服务
    Auth:'http://127.0.0.1:222',//授权服务
  }
}

在根目录添加service.online.js ,用于线上开发, 这个文件通过复制的方式把文件复制到打包的static的文进件 在wenpack.common.js里添加

var WEBPACK_ENV = process.env.scene.trim();

在通过模板htmlWebpackPlugin打包的时候,选择不同的模板,本地开发用template/index-dev.html.,开发的时候用template/index-prod.html, 分别写入不同的配置文件就行, 就达到了 即使修改 接口得ip也可以及时生效, 不需要重新打包的需求了。

十九、 Tree-Shaking和ES6 module

动态与静态

CommonJS与ES6 Module最本质的区别在于CommonJS对模块依赖的解决是“动态的”而ES6 Module是“静态的”。在这里“动态的”含义是,模块依赖关系的建立发生在代码运行阶段;而“静态”则是模块依赖关系的建立发生在代码编译阶段

blog.csdn.net/qq_42683219…

19.1 ES6 module 静态结构

当前的JavaScript模块格式具有动态结构:导入和导出的内容可以在运行时(runtime)更改。 ES6引入自己的模块格式的一个原因是启用静态结构。静态结构意味着您可以在编译时确定导入和导出(静态) --- 您只需要查看源代码,而不必执行它。

ES6在语法上强制执行:您只能在顶层导入和导出(而不嵌套在条件语句中)。

19.2 动态的CommonJS模块

如下代码,只有运行代码的时候才能知道导入的内容;

B文件
//calculator.js
module.exports={name:"calculator"};

A文件
//index.js
const name=require('./calculator.js').name;

当模块A加载模块B时,会执行B中的代码,并将其module.exports对象作为require函数的返回值进行返回。并且require的模块路径可以动态指定,并支持传入一个表达式或者一个if进行判断是否加载模块。因此可以看出,在commonJS模块被执行前,并没有办法确定明确的依赖关系,模块的导入、导出发生在代码的运行阶段。

19.3 ES6 模块 export import

//calculator.js
export const name='calculator';

//index.js
import {name} from './calculator.js';

ES6 Module的导入、导出语句都是声明式的,它不支持导入的路径是一个表达式,并且导入、导出语句必须位于模块的顶层作用域不能放到if语句中。因此ES6 Module是一个静态模块结构。

ES6 Module相比与CommonJS的优点:

  • 在捆绑期间消除死代码,优化打包之后的代码体积(Tree-Shaking)
  • 模块变量类型检查(静态的知道哪些变量在模块内的哪些位置可见)。
  • 更快查找到入口 (commonjs必须执行才能查找,很慢,因为是动态的)
  • 为类型做好准备(静态类型检查加强)

19.4 什么是Tree-Shaking?

Tree-Shaking是一个术语,通常用于描述移除Javascript上下文中的未引用的代码(dead-code)。它依赖于ES6 module语法的静态结构特性,如 import 和 export ,用来检测代码模块是否被导出、导入,且被Javascript文件使用。使用模块打包(webpack,rollup)将多个Javascript文件打包为单文件时自动删除未引用的代码。对于发布线上代码起到优化作用。这个术语和概念实际是由ES2015模块打包工具rollup普及起来的。

tree-shaking更关注于无用模块的消除,消除那些引用了但并没有被使用的模块。DCE 消灭不可能执行的代码。

在webpack 2正式版内置支持ES6模块和未使用模块检测能力。新的webpack 4正式版本扩展了此检测能力,通过 package.json 的"sideEffects"属性作为标记,向compiler提供提示,表明项目中的哪些文件是" pure(纯 ES2015 模块) ",由此可以安全的删除文件中未使用的部分。

19.5 使用Tree-Shaking 压缩代码(webpack 4)

webapck.config.js

const path = require('path')
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'budle.js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    usedExports: true
  }
}

将文件标记为 side-effect-free(无副作用):提示 webpack compiler 哪些代码是“纯粹部分”,通过 package.json 的 "sideEffects"属性,来实现这种方式

{
  "name": "webpack-4-demo",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --open --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
  },
  "devDependencies": {
  },
  "sideEffects": false
}

19.6 结论

在webpack 4版本中,直接对生产环境配置mode:'production',即可启用tree shaking,并将代码压缩, 然后在package.json文件加上"sideEffects": false,(注意,所有导入文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader 并 import 一个 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:)用来提示webpack compiler找出哪些代码是未被引用的,然后删除掉。

想要使用tree shaking 必须注意如下几点:

  • 使用es6 模块语法(import 和 export)
  • 确保没有compiler将ES6模块语法转换为CommonJS模块(Babel preset 中 @babel/preset-env 的默认值是auto,将module 设置为false将不会转换模块)
  • 在项目 package.json 文件中,添加一个"sideEffects"属性
  • 通过将 mode 选项设置为 production,启用 minification(代码压缩) 和 tree shaking。 你可以将应用程序想象成一棵树,绿色表示实际用到的 source code(源码) 和 library(库),是树上活的树叶。灰色表示未引用代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
{
    "name": "webpack-demo",
     //@babel/polyfill和css文件不使用tree shaking
    "sideEffects": [
        "@babel/polyfill",
        "*.css"
    ],
    "version": "1.0.0",
    "description": "",
}

二十、代码分割 code Splitting

import _ from "lodash";

console.log(_.join(['a','b','c','****']))

假如我们引入一个第三方的工具库,体积为1mb,而我们的业务逻辑代码也有1mb,那么打包出来的体积大小会在2mb

导致问题:

  • 体积大,加载时间长
  • 业务逻辑会变化,第三方工具库不会,所以业务逻辑一变更,第三方工具库也要跟着变。

引入代码分割的概念

lodash.js

import _ from "lodash";
window._ = _;

index.js

//注释掉lodash引用 //import _ from "lodash";
console.log(_.join(['a','b','c','****']))

webpack.config.js

entry: {    
    lodash: "./lodash.js",    
    index: "./index.js" 
 },  
//指定打包后的资源位置  
output: {    
    path: path.resolve(__dirname, "./build"),    
    filename: "[name].js" 
}

它的默认配置如下:

optimization: {    
    splitChunks: {      
        chunks: 'async',//对同步,异步,所有的模块有效      
        minSize: 30000,//当模块大于30kb      
        maxSize: 0,//对模块进行二次分割时使用,不推荐使用      
        minChunks: 1,//打包生成的chunk文件最少有几个chunk引用了这个模块      
        maxAsyncRequests: 5,//模块请求5次      
        maxInitialRequests: 3,//入口文件同步请求3次      
        automaticNameDelimiter: '~',      
        name: true,      
        cacheGroups: {        
            vendors: {          
                test: /[\\/]node_modules[\\/]/,          
                priority: -10//优先级 数字越大,优先级越高 
            },        
            default: {          
                minChunks: 2,          
                priority: -20,          
                reuseExistingChunk: true       
            }     
        }   		
    } 
 }

使用下面配置即可:

optimization:{    
    //帮我们自动做代码分割    
    splitChunks:{        
        chunks:"all",//默认是支持异步,我们使用all ,只设置这一个就可以工作
        //chunks:"async",//默认是支持异
    }
  }

二十一、代码按需加载

测试代码: //index.js

document.addEventListener("click", () => {  
    const element = document.createElement("div");  
    element.innerHTML = "welcome to webpack4.x";  
    document.body.appendChild(element); 
});

通过控制台看看代码利用率

把里面异步代码抽离出来 //index.js

document.addEventListener("click", () => {   
import("./click.js").then(({ default: func }) => {   
//需要用到 npm install --save-dev @babel/plugin-syntax-dynamic-import 

func();  
 });
}); 

//click.js

function handleClick() {   
const element = document.createElement("div");   
element.innerHTML = "welcome to webpack4.x";   
document.body.appendChild(element); 
} 
 
export default handleClick; 

以上情况是在点击 按钮以后才会动态去加载相应的js文件, 但是有可能你在点击的时候,会出现用户用等待的情况 而出现延时, 可否让浏览器把 主要的代码加载完毕后, 在空余的时间 去加载异步点击的代码呢?

使用预先拉取和预先加载提升性能

Webpack 4.6.0为我们提供了预先拉取(prefetching)和预先加载(preloading)的功能。使用这些声明可以修改浏览器处理异步chunk的方式。

  • 预先拉取

使用预先拉取,你表示该模块可能以后会用到。浏览器会在空闲时间下载该模块,且下载是发生在父级chunk加载完成之后。

import(
  `./utilities/divide`
  /* webpackPrefetch: true */
  /* webpackChunkName: "utilities" */
)

以上的导入会让被添加至页面的头部。因此浏览器会在空闲时间预先拉取该文件。

  • prefetch概念

为了帮助其它浏览器对某些域名进行预解析,你可以在页面的html标签中添加dns-prefetch告诉浏览器对指定域名预解析,如下:

<link rel="dns-prefetch" href="//domain.com">

DNS Prefetch,即DNS预获取,是前端优化的一部分。一般来说,在前端优化中与 DNS 有关的有两点: 一个是减少DNS的请求次数, 另一个就是进行DNS预获取