前言

咱们常见的打包工具比如vite,vuecli底层都是通过webpack实现的,下面就来对webpack进行一个学习并且实战运用

为什么需要webpack

这就要回顾一下没出现打包工具之前的弊端了。在以前,我们的js都是引入之后写在页面上的,有很多第三方的js比如jquery,lodash等,以及自己编写的js,此时需要注意引入的顺序,否则就会报错。可能会想到将它们合并到一个文件里面就不会报错,但是这样一来冗长的代码导致可读性差,可维护性弱的问题。此外还有作用域和文件太大的问题。

作用域问题

我们知道,像jq这种库,全局声明是$符号,这个符号是绑定在window对象上面的,而lodash是绑定的下划线,我们自己的业务文件可能也绑定了类似于user这样的变量到window上面。这样一来我们的window对象就会被污染了,使得它变得非常臃肿。这就是作用域问题。

文件太大

如果不合并,那么有多少个js,就加载多少个js,页面可能就会出现一边加载一边渲染的问题。
如果将文件合并,这个巨大的js就会带来网络瓶颈,比如首页白屏问题

before webpack

在webpack之前,我们使用的是grunt和gulp这两款工具来管理我们的项目资源,我们称之为任务执行器。它是通过IIFE来解决作用域问题。在IIFE中,文件可以安全拼接而不用担心作用域问题
IIFE包裹的东西是不能在外部访问的,所以它不会污染作用域。

1
2
3
4
(function(){
var jojo ='jojojo';
})()
console.log(jojo);//不行

但如果我们想要暴露出一些内容应该怎么做呢?
1
2
3
4
var res = (function(){
return 'ajsjdajsd';
})()
console.log(res);

我们的grunt和gulp就是采用这种方式进行管理的,但是由此就引申出了一个问题,如果我们的代码有1w行,我们修改了其中的一行,里面的内容就会重新编译,造成了不需要的性能浪费。
解决的办法是实现方法的懒加载或者把文件拆分成一个一个方法的模块

代码拆分

nodejs的commonjs提供了导入导出的功能,使得js代码可以模块化了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//math.js
const add = (x,y)=>{
return x + y;
}
const minus = (x,y)=>{
return x - y;
}
module.exports = {
add,
minus
}
//server.js
const math = require('./math.js');
console.log(math.add(4,5))//9

虽然在node上面支持模块化,但是我们的浏览器是不支持这样使用的,也就是只能在node环境下面才能用commonjs。

让浏览器支持模块化

我们为了能让浏览器支持模块化,使用的是browserify,requirejs这样的打包工具。下面使用requirejs来演示
新建requirejs文件夹 放入add.js,minus.js,main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//addjs
const add = (x, y) => {
return x + y;
}
define([], function () {
return add;
});
//minusjs
const minus = (x, y) => {
return x - y;
}
define([], function () {
return minus;
});
//mainjs
require(['./requirejs/add.js','./requirejs/minus.js'],function(add,minus){
console.log(add(4,5))
})

注意mainjs的引入路径是根据index.html来判断的。
1
2
3
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.js" data-main="./requirejs/main.js"></script>
</body>

引入requirejs从bootcdn,然后使用data-main定义文件入口。
除了requirejs,ecma其实也给我们提供了模块化的方法,比如以前学过的importexport
注意需要live server插件来保证同源
回顾一下export三种方式

  1. 分别暴露
    1
    2
    3
    4
    5
    6
    export let aa='xczxc';
    export function (){
    xxxx;
    }

    import * as m1 from 'xxxx';
  2. 统一暴露
    1
    2
    3
    4
    5
    let jojo = 'jostar';
    function sad(){
    xxxx;
    }
    export {jojo,sad};
  3. 默认暴露
    1
    2
    3
    4
    5
    6
    7
    export default {
    let jojo ='jostar';
    let jojoprocess = function(){
    xxxx
    }
    return xxx
    }

webpack的竞品

  1. parcel
    0配置 用户无需做其他操作,开箱即用。
    如果你想构建一个简单的应用并且让他快速的构建起来,可以使用parcel
  2. rollup.js
    用标准化的格式来编写代码,减少无用的代码来缩小包的体积
    如果你想构建一个类库,并且只用导入很少的第三方库,可以使用rollup
  3. vite

安装webpack

全局安装

确保有node环境

1
npm install webpack webpack-cli --global

一定要加global 否则失效
查看版本

1
webpack -v

全局安装的方式并不推荐,因为它会锁定你的webpack版本,导致后面不能使用别的版本的webpack。另外没有写入依赖,合作的时候就无法通过npmi来安装。

局部安装

注意项目的名字不能有中文

首先创建packgejson

1
npm init -y

然后安装
1
npm install webpack webpack-cli --save-dev

运行webpack

新建文件夹src 新建helloworldjs并暴露helloworld方法,新建indexjs导入方法并使用.
然后把全局的webpack卸载

1
npm uninstall webpack webpack-cli --global

之后使用webpack命令查看webpack还是否存在,发现已经不能使用.
接着使用命令
1
npx webpack

发现会产生一个dist文件,里面有打包的mainjs(注意此时会报一个mode的warning 但是并无大碍)
为什么webpack命令又可以运行了呢?因为npx会查找当前目录是否存在该命令,如果没有就去上一层找,而我们在最外层定义局部安装了webpack 所以可以使用.
1
(()=>{"use strict";console.log("helloworld")})();

自定义文件配置

上面提到打包后创建了mainjs,那么我们如何修改这个入口文件的路径和文件名呢?
先使用命令创建mainjs

1
npx webpack --entry ./src/index.js --mode production

这样就解决了mode问题.
但我们发现这样的方式并不方便直观,所以webpack提供了一个新的方式.在webpack.config.js里面去配置参数即可.
这个文件是建立在当前目录下面的请注意
注意:这个文件是在nodejs里面运行的,因此我们去定义模块的时候,得使用nodejs的commandjs模块
1
2
3
4
5
6
7
8
const path = require('path')
module.exports = {
entry:'./src/index.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'./dist')
}
}

我们引入路径的时候不能直接写相对路径.webpack要求一个绝对路径,所以说要使用到node模块中的path引入绝对路径.
再执行npx webpack发现main变成了bundlejs,而且它们两个完全不一样
那么现在打包完了 如何在浏览器上面运行代码?
引入bundlejs即可.(手动引入)

插件

自动引入资源

上面讲到了要引入打包后的bundlejs只能手动引入,那么有什么办法可以自动引入呢?
这就使用到了webpack的插件.

  1. 安装插件
    1
    npm install --save-dev html-webpack-plugin
  2. webpack.config.js中引入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    module.exports = {
    entry:'./src/index.js',
    output:{
    filename:'bundle.js',
    path:path.resolve(__dirname,'./dist')
    },
    mode:'none',
    plugins:[
    new HtmlWebpackPlugin()
    ]
    }
  3. 执行npx webpack即可看见dist目录下面打包的html了

插件配置项

通过插件配置项可以改变输出的名字和插入的js位置,但需要模板

1
2
3
4
5
6
7
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
filename:'jojo.html',
inject:'body'
})
]

注意上面的模板路径要正确。

清除上次dist

在output里面配置

1
2
3
4
5
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'./dist'),
clean:true
},

source-map调试

如果代码出错,我们打包后的html在浏览器中显示错误的位置并不是源文件中的位置,而是打包后的某个错误文件的位置,要想知道源文件错误的位置在哪里,需要配置devtool,而且必须放在plugins前面

1
devtool:'inline-source-map',

自动编译

每次都要npx webpack很麻烦 直接使用npx webpack --watch指令即可

热更新

每次都要刷新很麻烦

1
npm install webpack-dev-server --save-dev

配置文件中

1
2
3
devServer:{
static:path.resolve(__dirname,'./dist'),
}

webpack-dev-server没有输出任何的物理文件,他把输出的打包以后的bundle文件放到了内存里,就算把dist文件删掉也没有关系 类似于vue-cli

资源模块

webpack可以使用内置的资源模块来引入任何的其他类型资源,资源模块我们叫做asset module,是一种模块类型,允许我们用webpack来打包其他的资源文件,比如像字体文件,图标文件等等。资源模块类型称作为asset module type会通过四种新的类型模块来替换所有的loader

  1. asset/resourse会发送一个单独的文件并导出URL
  2. asset/inline会导出一个资源的Data URL
  3. asset/source会导出资源的源代码
  4. asset他会在导出一个Data URL和发送一个单独的文件之间进行选择。

asset/resource导出单独的文件

导出到指定的文件夹
方式1:全局

方式2:局部

适合处理大图片

asset/inline

不会生成资源,适用于svg,会转换成base64链接

asset/source

导出源代码 适合于渲染文本内容。

asset

自动选择url或者文件,依据是文件的大小,默认情况是8k以下base64 以上生成对应的文件。可以自定义
自定义方法如下:

loader

webpack只能解析js和json,loader提供了解析其他资源的可能,并且将这些文件转换为有效的模块

打包流程图:

那么loader怎么定义呢?

例子:

css-loader

安装

1
npm install css-loader -D

使用

但是我们在控制台中发现他并没有生成link来连接css,也没有对应的style放置css,所以一个cssloader是不够的

style-loader

安装

1
npm install style-loader -D

我们需要使用这个styleloader来帮助我们加载css样式,但是我们发现在use里面使用一个字符串就不合适了。此时改为数组的形式。且数组是有顺序的,我们应该先写styleloader 再写cssloader。原因是我们需要先用cssloader来帮助我们识别webpack文件,在通过styleloader加载我们的css

由此还得说明webpack支持链式调用,他的执行顺序是从后往前,第一个cssloader会讲结果或者是转后的源,传递给下一个loader,最后webpack希望styleloader会返回一个js,就实现了效果。

less-loader

执行方法

抽离和压缩css

上述讲述的loader会把他放在style里面,而我们更期望能够将他转换为一个单独的文件,用link进行一个加载,所以要如何操作?

  • 安装插件
1
npm install mini-css-extract-plugin -D
  • 取名

    1
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');



    这样加载出来的就会多个link标签

  • 但是我们会发现 这样的css并没有压缩,所以我们需要一个插件帮助我们压缩css

1
npm install css-minimizer-webpack-plugin -D
  • 定义变量
  • 需要将mode改为production
  • 配置项

加载图像

  • 需要将mode改为development
  • 使用assetModuleFilename

fonts字体

  • 加载格式

babel-loader

  • 安装
1
npm install -D babel-loader @babel/core @babel/preset-env
  • babel-loader:解析es6
  • @babel-core:核心模块
  • @babel/preset-env:babel预设一组babel的插件集合

regeneratorRuntime插件

上述步骤执行指令会浏览器报错 缺少该插件。

  • 安装
1
2
npm install --save @babel/runtime
npm install --save-dev @babel/plugin-transform-runtime
  • 使用

代码分离

常见的代码分离方式如下:

  1. 入口起点:使用entry配置手动分离代码
  2. 防止重复:使用Entry dependencies或者SplitChunksPlugin去重和分离代码
  3. 动态导入:通过模块的内联函数调用来分离代码

入口起点

这个方法需要配置入口文件,如下

但如果我们引入了相同的js库,他会分别打包到对应的入口,即会重复

防止重复

使用下面的配置方法抽离公共的库,这里抽离的是lodash,最后会变成一个单独的js文件

方法2:使用webpack内置的插件split-chunks-Plugin

  • 入口文件格式改回来
  • 配置

动态导入