webpack核心概念

webpack核心概念

一、loader

  loader就是一个打包的方案,在打包的过程中,webpack不认识非.js后缀的这些模块,但是loader知道对于某一个特定的文件(如.jpg)该如何打包。那么在配置项中file:loader就会帮助它来打包这些静态文件,同时会在dist文件夹下生成这些文件。

1.1 安装

1
npm install file-loader -D

1.2配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.export{
mode: 'development',
entry: {
main: './src/index.js'
},
module: {
rules:[
{
test:/\.txt$/,
use: {
loader: 'file-loader'
}
}
]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname,'dist')
}
}

1.3 option

没有option配置项时打包生成的文件名为哈希值,加上option时:
name:'[name].[ext]'代表打包出的结果,图片为原本的名字.后缀名
name:'[name]_[hash].[ext]'代表打包出的结果,图片为原本的名字_哈希值.后缀名

1.4 file-loader

  当遇到jpg png gif这样的文件时,用file-loader打包,输出路径为images,到时候会在dist目录下生成一个images的文件夹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.export{
mode: 'development',
entry: {
main: './src/index.js'
},
module: {
rules:[
{
test: /\.txt$/,
use: {
loader: 'file-loader',
option: {
name: '[name]_[hash].[ext]'
}
}
}
]
}
}

1.5 url-loader

  可以用url-loader代替file-loader的功能(安装:npm install url-loader -D),用url-loader打包是可以看到网页内容的,又不需要去找文件路径,少了一次http请求,(但是打包的时候,dist目录下没有图片文件,打开index.html却能看到图片,那是因为打包的时候把图片转成了base64)但是如果图片文件很大,打包生成的bundle.js文件也会相应很大,这样页面加载时候会很慢,那么正确使用url-loader的方式是什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module: {
rules:[
{
test:/\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
option: {
name: '[name]_[hash].[ext]',
outputPath: 'images/'
}
}
}
]
}

1.6 limit

  在option里面配置limit,当图片小于10240字节(10KB)时使用url-loader以base64形式打包到js文件里面,当大于10KB时用file-loader打包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module: {
rules:[
{
test:/\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
option: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240
}
}
}
]
}

1.7 style-loader、css-loader、sass-loader

  打包CSS文件,同样的,webpack不认识css后缀的文件,所以要在webpack.package.js里面去配置。

  安装style-loadercss-loader

1
npm install style-loader css-loader -D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module.export{
mode: 'development',
entry: {
main: './src/index.js'
},
module: {
rules:[
{
test: /\.txt$/,
use: {
loader: 'file-loader',
option: {
name: '[name]_[hash].[ext]'
}
}
},
{
test: /\.css$/,
use:[
'style-loader',
'css-loader'
]
}
]
}
}

  如果是sass这种比较新潮的样式文件的话安装sass-loadernode-loader

1
2
3
4
5
6
7
8
{
test: /\.scss$/,
use:[
'style-loader',
'css-loader',
'scss-loader',
]
}

  现在我们已经有3个loader了,在webpack的配置里,loader是有先后顺序的,它的执行顺序是:从下到上,从右到左。所以当我们去打包一个scss文件时,首先会执行sass-loader,对代码翻译成css,给到css-loader,处理好后给style-loader挂载到页面上。

1.8 postcss-loader

  有时候我们会写transform:translate(100px,100px)这样的样式,一般这样的样式都会有厂商前缀,比如-webkit-,那在加了postcss-loader之后,就不需要我们手动写了,它会自动把它添加上去。安装:

1
npm install postcss-loader -D

postcss要求我们在目录下创建一个postcss.config.js文件并安装autoprefixer这样的一个插件:

1
npm i autoprefixer -D

1
2
3
4
5
6
7
8
9
{
test: /\.scss$/,
use:[
'style-loader',
'css-loader',
'scss-loader',
'postcss-loader',
]
}

📁postcss.config.js

1
2
3
4
5
module.exports = {
plugins: [
require('autoprefixer')
]
}

1.9 importLoaders

  有时候会在scss文件里再引用一个scss文件(简称第二文件),在第一文件处理完后第二文件便直接从css-loader开始,到style-loader,这样的话就跳过postcss-loader和sass-loader了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
module.export{
mode: 'development',
entry: {
main: './src/index.js'
},
module: {
rules:[
{
test: /\.txt$/,
use: {
loader: 'file-loader',
option: {
name: '[name]_[hash].[ext]'
}
}
},
{
test: /\.css$/,
use:[
'style-loader',
{
loader: 'css-loader',
options:{
importLoaders: 2
}
},
'scss-loader',
'postcss-loader'
]
}
]
}
}

  所以要把css-loader写在一个对象里面,并在里面配置importLoaders:2,意思是第二文件也要从postcss-loader和sass-loader开始编译。

1
2
3
4
5
6
7
8
9
10
11
12
use:[ 
'style-loader',
{
loader: 'css-loader',
options:{
importLoaders: 2,
modules: true
}
},
'scss-loader',
'postcss-loader'
]

modules: true开启css的模块化打包,避免全局导入时对样式的影响。

1.10 打包字体

1
2
3
4
5
6
{
test: /\.(eot|ttf|svg)$/,
use:[
'file-loader'
]
}

不需要option配置

二、使用plugins插件打包文件更便捷

2.1安装:

1
npm install html-webpack-plugin -D

2.2 htmlwebpackplugin

  htmlwebpackplugin这个插件会在打包结束后,自动生成一个html文件,并把打包生成的js自动引入到这个html文件中。

📁webpack.config.js

1
const HtmlWebpackPlugin = require('html-webpack-plugin');

plugin可以在webpack运行到某个时刻的时候,帮你做一些事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
module.export{
mode: 'development',
entry: {
main: './src/index.js'
},
module: {
rules:[
{
test: /\.txt$/,
use: {
loader: 'file-loader',
option: {
name: '[name]_[hash].[ext]'
}
}
},
{
test: /\.css$/,
use:[
'style-loader',
{
loader: 'css-loader',
options:{
importLoaders: 2
}
},
'scss-loader',
'postcss-loader'
]
}
]
},
plugins:[
new HtmlWebpackPlugin({
template: 'src/index.html'
})
],
}

2.3 clean-webpack-plugin

  有时候我们打包后会留下上次打包在dist里的内容,如果想要覆盖,用clean-webpack-plugin就可以了,他是第三方插件,在官网上找不到文档。在打包执行之前运行

安装:

1
npm install clean-webpack-plugin -D

📁webpack.config.js

1
const CleanWebpackPlugin = require('clean-webpack-plugin');

1
2
3
4
5
6
plugins:[
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist'])
]

三、Entry 与 Output 的基础配置

  它会在打包生成的index.html中把main.js和sub.js放进去,是因为之前在打包配置文件里面使用了htmlwebpackplugin

1
2
3
4
5
6
7
8
9
10
11
module.export{
mode: 'development',
entry: {
main: './src/index.js'
sub: './src/index.js'
},
output:{
filename: '[name].js',
path: path.resolve(__dirname,'dist')
}
}

1
2
<script type="text/javascript" src="main.js"></script>
<script type="text/javascript" src="sub.js"></script>

如果我想要在打包之后注入的js前面带一个域名,可以用publicPath配置项

1
2
3
output:{
publicPath: 'http://cdn.com.cn'
}

打包之后所有引入的js前面都带有域名了

1
2
<script type="text/javascript" src="http://cdn.com.cn/main.js"></script>
<script type="text/javascript" src="http://cdn.com.cn/sub.js"></script>

四、SourceMap的配置

4.1 SourceMap

  在mode为development时,默认SourceMap时被配进去了,主动关闭SourceMap:devtool:'none'
  如想打包后出现报错,要想知道src目录下的文件哪一行代码出错而不是打包文件dist下的哪一行出错,例:

  • 当前index.js文件中的第一行出错
  • 现在知道dist目录下main.js文件第96行出错,SourceMap是一个映射关系,它知道dist目录下main.js文件第96行的出错实际上对应的是src目录下index.js文件中的第一行
    1
    2
    3
    4
    5
    6
    7
    module.export{
    mode: 'development',
    devtool: 'source-map'
    entry: {
    main: './src/index.js'
    }
    }

  它会生成一个map文件,他的内容是一种编码方式,用来对应文件的映射关系。

4.2 inline-source-map

inline-source-map的话,就不会在dist中生成这样的map文件,而是直接以base64形式放在了dist中的main.js的底部。

1
devtool: 'inline-source-map'

4.3 cheap-inline-source-map

  当我们遇到代码量很大的时候,inline-source-map会告诉我们错误会精确的告诉我们在第几行第几列,但是这样的一个映射比较耗费性能,代码出错了,我只希望你告诉我在第几行就可以,如果是cheap-inline-source-map,那么久不会把错误的第几行第几列告诉我们了,这样就可以在一定程度上提升打包的性能。

1
devtool: 'cheap-inline-source-map'

4.4 cheap-module-inline-source-map

不仅管业务代码的错误,还管loader等一些插件上的代码错误
cheap-module-inline-source-map

1
devtool: 'cheap-module-inline-source-map'

4.5 eval

  eval一样也可以找到错误第几行,main.js底部不会有map的编码,取而代之的是

1
eval("console.log('hello world');\n\n//# sourceURL=webpack....")

这样一种形式。eval这种方式是执行效率最快的,性能最高的打包方式,但是针对于比较复杂的代码用eval提示出来的内容可能并不全面。

所以最佳的使用方式是:
如果在开发环境中(development),建议使用devtool:cheap-module-eval-source-map;
如果把代码放到线上环境了(production),建议使用devtool:cheap-module-source-map;

五、使用 WebpackDevServer 提升开发效率

5.1 安装

1
npm run watch

  webpack watch会监听我们打包的文件,只要他要打包的源文件代码发生变化,就会自动的重新打包,从而提升打包效率。
安装WebpackDevServer:

1
npm install webpack-dev-server -D

1
2
3
devServer:{
contentBase: './dist'
}
1
2
3
4
"scripts":{
"watch": "webpack --watch",
"start": "webpack-dev-server"
}

运行:npm run strat,此时会有一个localhost

  WebpackDevServer相较于webpack watch,不但一样可以监听到源文件代码的变化,重新帮我们打包,它还会自动刷新浏览器,更方便地提升开发效率。

1
2
3
4
devServer:{
contentBase: './dist'
open: true
}

open是指自动打开一个浏览器,自动地访问地址http://localhost:8080

六、Hot Module Replacement 热模块更新

它可以在我们写css的时候方便我们调试CSS

1
2
3
4
5
6
7
devServer:{
contentBase: './dist',
open: true,
port: 8080,
hot: true,
hotOnly: true
}

📁webpack.config.js

1
const webpack = require('webpack');

1
2
3
4
5
6
7
plugins:[
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist']),
new webpack.HotModuleReplacementPlugin()
]

  我们都知道webpack-dev-server在代码改变之后它会自动打包并刷新页面,那有时候我们只更改了样式代码,所以用HotModuleReplacementPlugin就直接把更改的CSS代码给渲染到页面上而不会整一个重新自动打包并刷新页面了,这样就很方便调试。

在配置好之后最好重启一下命令npm run start

例:counter模块里面的js代码是点击数字累加1;number模块里面的代码是显示数字1000;想要实现的效果是当点击数字1,累加到10,这时改变数字1000为2000,在页面上不重新刷新,10还是10,1000已经变为2000

1
2
3
4
5
6
7
8
9
10
11
12
13
import counter from './counter';
import number from './number';

counter();
number();

//如果配置了HotModuleReplacementPlugin,这个时候还需要正在index.js写判断语句
if (module.hot){
modele.hot.accept('./number',()=>{
document.body.removeChild(document.getElementById('number'))
number();
})
}

七、使用 Babel 处理 ES6 语法

bable可以将js语法书写的代码(ES6)输出为浏览器兼容的代码(ES5)

7.1 安装:

1
npm install --save-dev babel-loader @bable/core

在webpack.config.js中的rules加一条:
📁webpack.config.js

1
2
3
4
5
module:{
rules:[
{test:/\.js$/,exclude:/node_modules/,loader:"bable-loader"}
]
}

  exclude表示如果你的js文件在node_modules里面,就不使用bable-loader了,因为node_modules里面的代码是第三方的代码,没必要对这些第三方代码进行ES6→ES5的操作,其实第三方早就已经帮我们做好了这一步。所以文件在src目录下的,就会使用bable-loader

7.2 preset-env

preset-env包含了所有ES6→ES5的规则
安装:

1
npm install @bable/preset-env --save-dev

配置:

1
2
3
4
5
6
7
8
9
10
11
12
module: {
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: "bable-loader",
options:{
presets:["@bable/preset-env"]
}
}
]
}

  preset-env语法翻译只是翻译了一部分,虽然箭头函数已经变成普通函数的形式了,但是像map这种方法并不能让所有的浏览器兼容。所以这个时候我们需要借助polyfill帮我们对低版本浏览器的补充。

7.3 polyfill

安装:

1
npm install --save @bable/polyfill

📁 src  index.js

1
import "@bable/polyfill"

这个时候在presets里配置useBuiltIns : 'usage',这样的话就用哪个打包哪个,没有用到map函数,就不会把map函数翻译出来,这样就会减小打包体积。

1
2
3
4
5
6
7
8
9
10
11
12
module: {
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: "bable-loader",
options:{
presets:[["@bable/preset-env"],{useBuiltIns:'usage'}]
}
}
]
}

  假如运行在Chrome浏览器上,由于Chrome浏览器67以上的版本很好的支持了ES6,再转换成ES5没有意义,当版本大于67时不用preset-env打包。

1
2
3
4
5
6
7
8
9
options:{
presets:[
["@bable/preset-env"],
{useBuiltIns:'usage'},
targets:{
chrome:"67"
}
]
}

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×