Webpack 相关

1、 Loader是什么?

1、我们之前打包的都是js文件,下面试试打包一个图片文件。

首先将一个图片文件放进src目录下,接着添加index.js的模块引入代码:

var Header = require('./header.js');
var Sidebar = require('./sidebar.js');
var Content = require('./content.js');
var Logo = require('./logo.png');
 
new Header();
new Sidebar();
new Content();

执行npm run bundle后发现报错,原因是webpack是默认知道如何处理js模块的,但是不知道图片这种文件该如何去打包。所以我们应该主动告诉他如何去打包图片文件,这就需要我们自己去配置文件了。

webpack.config.js:

const path = require('path');
 
module.exports = {
  mode: 'production',  //意思是打包后的文件被压缩,我们不配置mode的话默认值是被压缩,但是会警告。
  entry: './src/index.js',
  //module的意思是当不知道如何去打包的时候,webpack就会去模块这个配置里面去找
  module: {
    //rules是规则,是一个数组
    rules: [{
      //假设我们打包的模块是以.png结尾的文件
      test: /\.png$/,
      //就应该这么去打包:用一个file-loader来打包该文件。(首先需要安装file-loader这个工具)
      use: {
        loader: 'file-loader'
      }
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

安装file-loader工具: npm install file-loader -D
安装完成之后执行打包命令,发现可以正常打包成功。

image.png

可以发现webpack把该图片也打包进了dist文件夹里面
我们尝试在index.js文件中console.log(Logo),然后重新打包,完成之后在控制发现打印出了图片被打包之后的文件名。

image.png

其实Loader就是一个打包方案,它知道对于某一个特定的文件,webpack应该如何进行打包,本身webpack是不知道该如何对一些文件进行处理的,但是Loader知道,所以webpack去求助于Loader就可以了。

2、例子:将打包后的图片插入到页面中

将三个模块的js文件都删除,修改index.js中的代码:

import Logo from './logo.png';
 
var img = new Image();
img.src = Logo;
 
var root = document.getElementById('root');
root.append(img);

重新打包,完成之后图片插入到id为root的dom下面了。

本节主要介绍的就是Loader的作用:webpack不能识别非js结尾的模块,就需要通过Loader让webpack识别出来。

类似的像我们之前import以vue结尾的文件,Webpack也是不能识别的,所以也需要通过loader来帮助它识别。


2、 使用Loader打包静态资源(图片篇)

1、我们前面对图片进行了打包,可是打包过后的文件名字是一个很长的字符串,我们希望打包后的文件名和打包之前一样。

配置文件webpack.config.js

const path = require('path');
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.png$/,
      use: {
        loader: 'file-loader',
        //使用loader的时候,可以配置一些参数,这些参数放在options这个对象中
        options: {
          name: '[name].[ext]'   //[name]表示打包之前的名字, [ext]表示打包之前的后缀
        }
      }
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

重新打包过后文件目录:


image.png

[name].[ext]这种配置的语法叫做placeholder,中文叫占位符。

我们可以在test配置中一次添加多个后缀:


image.png

2、我们希望打包后的图片文件被保存在一个images文件夹里面。

配置文件:

const path = require('path');
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/'   //意思就是将以png或jpg或gif结尾的打包文件打包过后放在dist文件夹下的images文件夹下面
        }
      }
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

打包完成之后的目录结构:


image.png

file-loader的配置特别多,以后会遇到很多图片打包的问题,如果不知道如何去处理,可以去file-loader的文档查找。

3、url-loader

url-loader除了能做file-loader的工作之外,还能做一个别的事情。

将配置文件的file-loader改成url-loader:

const path = require('path');
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',   //url-loader可以完成file-loader能完成的任何功能
        options: {
          name: '[name].[ext]',
          outputPath: 'images/'
        }
      }
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

首先尝试下能否打包,发现报错。原因是我们没有安装url-loader。执行安装命令:npm install url-loader -D

安装成功之后,执行打包命令,打包成功后的目录文件如下:


image.png

发现图片文件没有被打包,但是页面却将图片显示了出来。去控制台查看发现,图片的地址不是外部引入的,而是一个base64的内容:


image.png

原因是url-loader和file-loader不一样,会把我们的图片转换成一个base64的字符串,然后直接放到bundle.js里面,而不是单独生成一个图片文件。

优点:图片打包到js里面,实际上加载好了js,页面就出来了,也就不用额外去请求一个图片的地址了。省了一次http请求。

缺点:如果图片很大,打包生成的js文件也会很大,那么加载js的时间就会很长,所以一开始页面可能很长时间什么都显示不出来。

所以url-loader的最佳使用场景是:如果一个图片很小,就几kb,那么打包成base64的形式是非常好的选择。没必要让几kb的图片去发http请求,很浪费时间。如果图片很大,那么最好使用file-loader打包到dist目录下,不要打包到js里面。因为这样可以让bundle.js快速加载完成,页面可以快速的显示出来,不然bundle.js会很大,导致页面很久才能显示出来。解决办法:

添加配置:

const path = require('path');
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048   //意思是如果图片的大小超过了2048个字节的话,那么就会像file-loader一样,把它打包到dist文件夹下。小于2048个字节即小于2kb的话,会把它打包成一个base64的字符串。放到bundle.js里面。
        }
      }
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

3、 使用Loader打包静态资源(样式篇1)

1、接着之前的图片,我们需要图片大小是150x150,这时我们就需要一个样式文件来修饰这张图片。

index.css:

.logo {
  width: 150px;
  height: 150px;
}

index.js:

import Logo from './logo.png';
import './index.css';
 
var img = new Image();
img.src = Logo;
img.classList.add('logo');
 
var root = document.getElementById('root');
root.append(img);

此时图片的样式是不会改变的,我们需要告诉webpack去打包这个css文件。打包css文件要用到两个loader,style-loader和css-loader。执行npm install命令安装这两个loader。然后配置webpack.config.js。

webpack.config.js:

const path = require('path');
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048   //意思是如果图片的大小超过了2048个字节的话,那么就会像file-loader一样,把它打包到dist文件夹下。小于2048个字节即小于2kb的话,会把它打包成一个base64的字符串。放到bundle.js里面。
        }
      }
    }, {
      test: /\.css$/,
      use: ['style-loader', 'css-loader']
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

执行打包命令,成功之后页面中图片的大小就是150x150啦。

  • css-loader:
    添加一个logo.css文件:
.logo {
  width: 150px;
  height: 150px;
}

index.css:

@import './logo.css';

index.js文件不变,此时执行打包命令也能正常地实现效果。

css-loader作用:css-loader会帮我们分析出几个css文件之间的关系,最终把这几个Css文件合并成一段css。

  • style-loader
    作用:在得到css-loader生成的css内容之后,style-loader会把这些内容挂载到页面的head部分,

2、有时候我们用的不是css编写样式, 而是使用less或者scss等比较新的技术来编写。

删除logo.css文件,修改index.css文件为index.scss:

body {
  .logo {
    width: 150px;
    height: 150px;
  }
}

index.js中引入index.scss。

此时,很显然是不能打包成功的,此时还需要借助sass.loader来帮助我们编译scss文件。安装命令:

npm install sass-loader node-sass -D
安装成功以后修改配置:

const path = require('path');
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader']
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

执行打包命令即可成功。

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

3、使用css3的时候,自动帮助我们添加厂商前缀-------postcss-loader。

首先npm install postcss-loader -D进行安装;

接着安装一个插件,npm install autoprefixer -D;

在lessons目录下创建一个postcss.config.js文件

module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

配置文件:

const path = require('path');
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        'css-loader',
        'sass-loader',
        'postcss-loader'
      ]
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

index.scss:

body {
  .logo {
    width: 150px;
    height: 150px;
    transform: translate(200px, 200px);
  }
}

最后执行npm run bundle打包,可以发现对于不支持该属性的浏览器会自动给我们添加了css3浏览器厂商前缀。就是postcss-loader里面的autoprefixer插件帮我们添加的。


4、 使用Loader打包静态资源(样式篇2)

1、介绍一下css-loader的配置


image.png

我们如果需要对引入的css-loader进行配置的时候,就不要用字符串的形式写,用一个对象来包裹。options: {importLoaders: 2}意思是当我们在scss文件中也用了@import 引入scss文件的时候。

image.png

对于index.js里面引入的index.scss文件,他会依次执行从下往上的loader过程。但是打包index.scss的过程中,index.scss内部又额外引入了一个scss文件,那么额外引入的scss文件可能就不会走下面的postcss-loader和sass-loader过程,而是直接去走css-loader的过程。如果我们也希望额外引入的scss文件也走下面两个loader的流程。那么我们就需要使用importLoaders,并且给它设置成2。

2、Css打包的模块化

看一个例子

目录结构:


image.png

createLogo.js:

import Logo from './logo.png';
 
function createLogo() {
  var img = new Image();
  img.src = Logo;
  img.classList.add('logo');
 
  var root = document.getElementById('root');
  root.append(img);
}
 
export default createLogo;

index.js:

import Logo from './logo.png';
import './index.scss';
import createLogo from './createLogo.js';
 
createLogo();
 
var img = new Image();
img.src = Logo;
img.classList.add('logo');
 
var root = document.getElementById('root');
root.append(img);

最后进行一次打包,效果图如下:


image.png

可以发现这两个图片的样式一模一样。所以发现虽然我们只在index.js中引入了index.scss文件,但是createLogo.js里面的函数创造的图片样式也是一样的。这是因为我们样式这么引入的话就是全局的,从而会导致一些问题。从而引入了CSS Module的概念,即CSS 模块化的概念:表示这个CSS只在这个模块里有效。

webpack.config.js:

const path = require('path');
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2,
            modules: true    //意思就是开启css的模块化打包
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

index.js:

import Logo from './logo.png';
import style from './index.scss';
import createLogo from './createLogo.js';
 
createLogo();
 
var img = new Image();
img.src = Logo;
img.classList.add(style.logo);
 
var root = document.getElementById('root');
root.append(img);

再次打包之后,发现此时两个图片的样式就不一样了。

如果想要另一个图片的样式也一样,修改createLogo.js文件:

import Logo from './logo.png';
import style from './index.scss';
 
function createLogo() {
  var img = new Image();
  img.src = Logo;
  img.classList.add(style.logo);
 
  var root = document.getElementById('root');
  root.append(img);
}
 
export default createLogo;

再次打包即可。

使用上述的方法带来的好处就是能够保证各个模块之间样式的独立,能够避免很多的问题。

3、如何使用webpack打包字体文件。

删除一些文件后的目录如下:


image.png

下载几个iconfont图标下来,将里面的.eot、.ttf、.svg、.woff结尾的四个文件复制到src目录下新建的font文件夹中,然后将iconfont.css的代码复制到index.scss中。然后修改iconfont字体的引用路径。

目录结构:

image.png

index.scss中的代码就是iconfont.css的代码。

index.js:

import './index.scss';
var root = document.getElementById('root');
 
root.innerHTML = '<div class="iconfont icon-jiantou">abc</div>';

此时执行打包命令,发现报错。这是因为webpack会去打包index.scss文件,但是index.scss文件里又引入了很多类型的字体文件,这个字体文件webpack不知道该如何去打包。所以我们应该去配置中告诉它该如何去打包。

配置文件:

const path = require('path');
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {    //打包字体文件配置
      test: /\.(eot|ttf|svg|woff)$/,    
      use: {
        loader: 'file-loader'
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

执行打包命令即可。注意别忘了去掉模块化打包配置,否则没有效果:

image.png

5、使用plugins让打包更快捷

1、我们dist目录下有一个index.html文件,假如我们将dist文件夹删除,再进行打包,打包之后的dist文件夹里面没有index.html文件,每次都需要手工添加该文件。这就需要一个插件帮助我们自动生成它。

首先npm install html-webpack-plugin -D安装该插件。

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');     //引入该插件
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.(eot|ttf|svg|woff)$/,    
      use: {
        loader: 'file-loader'
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [new HtmlWebpackPlugin()],    //配置plugins
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

此时我们打包生成的dist目录下就有一个index.html文件了

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

但是目前该文件里面没有我们需要的id等于root的div标签。如果我们需要自动添加该标签,可以这么配置。

首先,在src目录下,新建一个index.html文件,添加代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.(eot|ttf|svg|woff)$/,    
      use: {
        loader: 'file-loader'
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [new HtmlWebpackPlugin({
    template: 'src/index.html'   //意思是当打包完成之后会生成一个html,此时生成的html是以src/index.html为模板生成的。
  })],
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

然后执行打包命令,此时dist目录下的index.html文件:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
<script type="text/javascript" src="bundle.js"></script></body>
</html>

页面也有效果了。

plugin 作用:可以在webpack运行到某个时刻的时候,帮你做一些事情。有点类似vue的生命周期函数

2、我们修改一下webpack.confitg.js,将output中的filename修改为dist.js。再次打包。

打包完成之后发现虽然index.html中引用的js文件也变成了dist.js,但是dist文件夹中之前打包的bundle.js却还存在,有时候这样会出现一些问题。所以我们需要先将bundle.js删除,再进行打包。要实现这个功能,我们就需要一个插件:cleanWebpackPlugin

image.png

首先进行安装:npm install clean-webpack-plugin -D

接着进行配置:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');   //引入该插件
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.(eot|ttf|svg|woff)$/,    
      use: {
        loader: 'file-loader'
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }), 
    new CleanWebpackPlugin()    //表示在打包之前会使用CleanWebpackPlugin这个插件帮助我们去删除dist目录下的所有内容
  ],
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

dist目录:


image.png

可以发现之前的dist.js就不存在了,只有bundle.js了。而且页面也有效果。


6、Entry和Output的基础配置

1、我们将webpack.config.js文件中的output的filename删除,再次打包。可以发现webpack打包的js文件默认名为main.js。

image.png

entry: './src/index.js'这个写法和图片

image.png

的写法是一样的。entry对象中main的意思是'./src/index.js'打包后生成的文件对应的名字是main.js。

2、有个新需求,希望把src目录下的index.js反复打包两次,生成两个文件,第一个叫main.js,第二个叫sub.js。

如果此时我们output中的filename设置成bundle.js,可以发现会打包错误。想要解决这个问题,我们可以将bundle替换成一个占位符[name],name的意思就是打包entry的key值,

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
 
module.exports = {
  mode: 'production',
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.(eot|ttf|svg|woff)$/,    
      use: {
        loader: 'file-loader'
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }), 
    new CleanWebpackPlugin()
  ],
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}     

执行打包命令之后可以看到成功打包了两个js文件:

image.png

3、有时候会把打包完的文件index.html给后端,作为后端的一个入口文件。但是会把js文件上传到CDN这样一个域名下面。此时html文件的引入地址就需要修改了。如何实现打包完之后自动在前面添加CDN的域名。

webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');

module.exports = {
mode: 'production',
entry: {
main: './src/index.js',
sub: './src/index.js'
},
module: {
rules: [{
test: /.(png|jpg|gif)/, use: { loader: 'url-loader', options: { name: '[name].[ext]', outputPath: 'images/', limit: 2048 } } }, { test: /\.(eot|ttf|svg|woff)/,
use: {
loader: 'file-loader'
}
}, {
test: /.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin()
],
output: {
publicPath: 'http://cdn.com.cn', //给index.html文件引入地址之前添加CDN地址
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
重新打包之后查看index.html文件:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
<script type="text/javascript" src="http://cdn.com.cn/main.js"></script><script type="text/javascript" src="http://cdn.com.cn/sub.js"></script></body>
</html>

所以,如果我们的项目后台用index.html,而静态资源放到cdn上的情况下,此时就要用到output的publicPath这个配置项;


7、SourceMap的配置
删除一些文件后的目录结构:

image.png

index.js:

console.log('hello world');

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
 
module.exports = {
  mode: 'production',
  devtool: 'none',   //当前处于开发者模式,SourceMap默认是打开的,此配置可以将它关闭
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.(eot|ttf|svg|woff)$/,    
      use: {
        loader: 'file-loader'
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }), 
    new CleanWebpackPlugin()
  ],
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}     

此时我们将sourcemap关闭了,如果js文件代码编写错了,打包之后控制台报错,只能看出是打包后的js文件某一行出错,而不能知道打包前是哪个文件出错。

image.png

解决办法:

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
 
module.exports = {
  mode: 'production',
  devtool: 'source-map',   //开启sourcemap
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.(eot|ttf|svg|woff)$/,    
      use: {
        loader: 'file-loader'
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }), 
    new CleanWebpackPlugin()
  ],
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}     

此时打包过后,控制台报错会指出具体哪个js文件的哪一行出错了。而且dist目录下也多出了一个main.js.map文件,里面就是一些映射关系。

devtool的各个配置值的含义:

image.png
image.png
  • source-map: 可以看到它的build是--,表示打包速度比较慢。
  • inline-source-map: 也会将错误指示到具体的源文件中,不过它对比source-map,没有map.js文件。其实用这个配置的话,他会把这个
    map文件通过dataUrl的方式直接写在main.js里面。
  • cheap前缀:只指出错误在哪一行即可,不用精确到哪一列。打包性能会得到一定提升。还有一个作用:如果写了它,那么sourmap只针对业务代码,比如我们的业务代码只在index.js中,那么我们的打包只会映射这个文件和打包生成的main.js之间的关系。不会管引入的一些第三方模块loader的错误如何做映射。如果也想第三方模块的错误映射的话,可以加module
  • eval:是打包速度最快并且性能最好的一种打包方式, 但是如果代码比较复杂,eval提示的错误可能不会那么全面。

最佳实践:如果在开发环境(development)中使用sourcemap的话,建议使用cheap-module-eval-source-map这种形式,这种方式提示的错误比较全,同时打包的速度也是比较快的。如果代码要放到线上了,此时就应该处于生产环境(production),一般上线环境的代码没有必要让它有一个sourcemap的映射,直接放到线上即可。如果我们也想出现错误以后能够快速的定位问题,那么此时可以配置成cheap-module-source-map,这样提示效果会更好。


8. 使用WebpackDevServer 提升开发效率

1、我们希望修改了src目录下的源代码后,webpack自动地去帮我们打包,而不需要我们用命令行的形式去手动打包。

方法一:
修改package.json:

image.png

意思是webpack会帮我们去监听它打包的文件,只要打包的文件变化,就会去自动打包。我们可以通过修改index.js打印的东西去控制台查看就可以发现。

方法二:

我们第一次执行npm run watch的时候,自动帮我们实现打包,同时自动打开浏览器,同时开可以去模拟服务器上面的一些特性。

安装webpackdevserver,命令:npm install webpack-dev-server -D

package.json:


image.png

webpack.config.js:


image.png

执行:npm run start 来启动Webpack devserve

可以发现这个时候就自动帮我们起了一个localhost:8080的服务器了。

image.png

可以在浏览器打开它,同时修改源文件也会自动打包并且自动刷新浏览器。

小知识点:


image.png

我们为什么要开一个服务器?

写过vue或react的都知道,有时候前端发送ajax请求,如果通过文件的形式在浏览器直接打开html文件的话,这时候如果要发送ajax请求是不可以的。因为要求所在的html必须在一个服务器上通过http协议的方式打开,这就是借助webpack devser开启一个服务器的原因。此时发送ajax请求就没有问题了。

我们平时使用vue或react的脚手架工具它都会帮我们去开启一个服务器,实际上大多数都是直接使用Webpack devserver帮助我们开启出来的。

2、之前比较老的脚手架工具里面没有使用webpackdevser这个工具,而是自己实现了一个类似这个功能的工具。

package.json添加配置:

  "scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch",
    "start": "webpack-dev-server",
    "server": "node server.js"
  }

意思就是我运行npm run middleware的时候,我想自己写一个服务器,这个服务器如果监听到src目录下的内容有改变,它会像webpackdevserver一样自动的帮我们去重启这个服务器,更新网页上的内容。

  • 安装express帮助我们快速去创建一个服务器, 服务器要去监听Webpack的变化,然后帮我们重新的去打包,所以还需要借助一个Webpack的开发中间件webpack-dev-middleware:npm install express webpack-dev-middleware -D
  • webpack.config.js:
image.png

然后在lesson目录下创建一个server.js文件:

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
const complier = webpack(config);   //webpack函数传入config,结果返回一个编译器
 
const app = express();
app.use(webpackDevMiddleware(complier, {
  publicPath: config.output.publicPath   //意思就是只要文件发生改变了,complier就会重新运行。重新运行生成的文件对应的打包输出内容的publicPath就是config.output.publicPath
}))
 
app.listen(3000, () => {
  console.log('server is running');
});

运行npm run server命令,发现成功打包,浏览器中也答应出内容。我们修改index.js打印的内容,刷新浏览器后发现控制台也跟着改变了,说明又自动打包了。只不过目前没有webpackdevserver那么智能。想要实现那么智能的效果需要耗费很多的精力,我们只需要了解可以这么写就行。


9. Hot Module Replacement(热模块替换HMR)

在使用webpackdevserver的时候,打包之后我们可以发现没有dist这个目录。实际上webpackdevserver还是会对src目录下的代码进行打包,但是打包生成的文件不会放在dist目录下,而是放在电脑内存下面。这样可以有效提升打包的速度,让我们的开发更快。

删除server.js文件,修改一些配置

目录:


image.png

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
 
module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',   //开启sourcemap
  entry: {
    main: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true,    //启动webpackdevser的时候(即 npm run start)会自动帮我们去打开一个浏览器,然后自动访问服务器的地址。
    port: 10000
  },
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.(eot|ttf|svg|woff)$/,    
      use: {
        loader: 'file-loader'
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {                    //增加一个css loader
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }), 
    new CleanWebpackPlugin()
  ],
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}     

package.json:


image.png

index.js:

import './style.css';
 
var btn = document.createElement('button');
btn.innerHTML = '新增';
document.body.appendChild(btn);
 
btn.onclick = function() {
  var div = document.createElement('div');
  div.innerHTML = 'item';
  document.body.appendChild(div);
}

style.css:

div:nth-of-type(odd) {
  background: yellow;
}

浏览器打开这个项目,我们多次点击新增按钮,页面效果:


image.png

此时我们去修改style.css文件,将颜色改为blue。发现页面中的div不存在了。我们希望之前渲染的元素不要修改,只修改样式,此时我们就可以借助HMR来帮助我们实现该功能。

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const webpack = require('webpack');    //引入webpack
 
module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  entry: {
    main: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true,
    port: 10000,
    hot: true,    //开启HMR功能
    hotOnly: true    //即便HRM功能没有生效,我也不让浏览器自动的刷新
  },
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.(eot|ttf|svg|woff)$/,    
      use: {
        loader: 'file-loader'
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }), 
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin()    //使用webpack的HMR功能
  ],
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}     

此时npm run start重新运行webpack,发现再次修改颜色就不会产生上述问题了。

多个js文件模块之间不想相互影响时,也可以使用HMR,配置都差不多

image.png

当我们在代码里引入其它的模块的时候,如果我们希望这个模块的代码发生了变化,只去更新这个模块这部分的代码,那就需要使用HMR。HMR的优点:方便我们调试css和js


10. 使用Babel处理ES6的语法(1)

  • 安装babel-loader插件npm install --save-dev babel-loader @babel/core

  • 添加webpack.config.js配置:


    image.png
  • 安装babel/preset-env插件帮我们将es6翻译成es5:npm install @babel/preset-env --save-dev
    这个时候我们写es6语法就会被翻译成es5的语法了。但是这样配置语法只是翻译了一部分,还有一些没有翻译的语法在低版本浏览器中还是不能识别。我们还需要一个polyfill插件帮我们将一些变量或者对象在低版本浏览器中的补充。

  • 安装命令:npm install --save @babel/polyfill

  • 在业务代码index.js的最顶部引入:import "@babel/polyfill";
    重新npx webpack进行打包,打包之后发现main.js的文件比之前大了很多,这就是polyfill要去弥补之前低版本浏览器不存在的一些内容。实际中我们不希望它把所有缺失的内容都保存进main.js中,只需要保存一些我们用了但是低版本浏览器缺失的内容。增加配置:

image.png

使用npx webpack进行打包,可以发现main.js小了很多。

可以单独将babel的options写到.babelrc文件下:

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

此时webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 
 
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }, {
      test: /\.jpg$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  output: {
    path: path.resolve(__dirname, 'bundle'),
    filename: '[name].js'
  }
}

index.js:

import "@babel/polyfill";
 
const arr = [
  new Promise(() => {}),
  new Promise(() => {})
];
 
arr.forEach((item) => {
  console.log(item);
})

11. 使用Babel处理ES6的语法(2)

1、指定浏览器的版本

webpack.config.js:

{
      test: /\.js$/,
      exclude: /node_modules/,
      loader:'babel-loader',
      options: {
        "presets": [['@babel/preset-env', {
          targets: {
            chrome: '67'   //意思是本次项目会运行在67版本以上的chrome浏览器上,这样打包的文件更小,因为67版本时有一些用到es6的代码不需要polyfill补充,因为此时该浏览器可能已经识别了。
          },
          useBuiltIns: 'usage'
        }]]
      }
    }

打包之后发现main.js更小了。

注意:如果在编写一些业务代码的时候,需要用到babel,配置方案可以参考在这之前讲的那一套配置即可。

2、开发一个类库、第三方模块或者一个组件库的时候用babelpolyfill这个方案实际上时会有问题的。因为它翻译一些es6语法时会通过全局变量的方式来注入,会污染到全局环境。所以要打包一个ui组件库等情况下的时候要换一种配置的方式。

  • 安装插件:npm install --save-dev @babel/plugin-transform-runtime
  • 安装插件:npm install --save @babel/runtime
  • 安装插件:npm install --save @babel/runtime-corejs2
    通过babel文档查找配置,webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const webpack = require('webpack');
 
module.exports = {
  mode: 'production',
  devtool: 'cheap-module-eval-source-map',
  entry: {
    main: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    port: 10000,
    hot: true
  },
  module: {
    rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader:'babel-loader',
      options: {
        // "presets": [['@babel/preset-env', {
        //   targets: {
        //     chrome: '67'   //意思是本次项目会运行在67版本以上的chrome浏览器上,这样打包的文件更小,因为67版本时有一些用到es6的代码不需要polyfill补充,因为此时该浏览器可能已经识别了。
        //   },
        //   useBuiltIns: 'usage'
        // }]]
        "plugins": [["@babel/plugin-transform-runtime", {
          "corejs": 2,   
          "helpers": true,
          "regenerator": true,
          "useESModules": false
        }]]
      }
    },
    {
      test: /\.(jpg|png|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 10240
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}

index.js:

const arr = [
  new Promise(() => {}),
  new Promise(() => {})
];
 
arr.forEach((item) => {
  console.log(item);
})

配置完毕,打包即可。该方案适合打包一个ui组件库等情况。

3、当babel的配置很多的时候,可以在根目录下创建一个.babelrc文件,将配置代码放进去。

.babelrc:

{
  "plugins": [["@babel/plugin-transform-runtime", {
    "corejs": 2,   
    "helpers": true,
    "regenerator": true,
    "useESModules": false
  }]]
}

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const webpack = require('webpack');
 
module.exports = {
  mode: 'production',
  devtool: 'cheap-module-eval-source-map',
  entry: {
    main: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    port: 10000,
    hot: true
  },
  module: {
    rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader:'babel-loader',
    },
    {
      test: /\.(jpg|png|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 10240
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}

配置完毕,打包即可。


11. Webpack 实现对React框架代码的打包

使用babel/preset-react插件来实现对react的jsx语法进行打包。在babel中查找相应的文档:

  • 要编写react的代码,就要安装react:npm install react react-dom --save
  • 安装命令:npm install @babel/preset-react -D
    添加配置

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const webpack = require('webpack');
 
module.exports = {
  mode: 'production',
  devtool: 'cheap-module-eval-source-map',
  entry: {
    main: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    port: 10000,
    hot: true
  },
  module: {
    rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader:'babel-loader',
    },
    {
      test: /\.(jpg|png|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 10240
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}

.babelrc:

{
  presets: [
    [
      "@babel/preset-env", {    //再转换一下es6的代码
        targets: {
          chrome: "67"
        },
        useBuiltIns: "usage"
      }
    
    ],
    "@babel/preset-react"    //先转换一下react的代码
  ]
}

index.js:

import '@babel/polyfill';   //打包业务逻辑代码
 
import React, { Component } from 'react';
import ReactDom from 'react-dom';
 
class App extends Component {
  render() {
    return <div>hello world</div>
  }
}
 
ReactDom.render(<App />, document.getElementById('root'));

执行打包命令后,App这个类就成功渲染进dom中了。


Tree Shaking 概念详解

index.js:

import '@babel/polyfill';
 
import React, { Component } from 'react';
import ReactDom from 'react-dom';
 
class App extends Component {
  render() {
    return <div>hello world</div>
  }
}
 
ReactDom.render(<App />, document.getElementById('root'));

实际上当前版本的Webpack比较新,如果在webpack.config.js里面配置了babel-loader的相关内容(其实我们已经把它移到.babelrc这个文件中了),如果我们对presets下的useBuiltIns设置了usage这样的一个配置参数的话。那么我们index.js文件中不去引入babel/polyfill也是可以的。

2、

目录:


image.png

index.js:

import {add} from './math.js';
 
add(1, 2);

math.js:

export const add = (a, b) => {
  console.log(a + b);
}
 
export const minus = (a, b) => {
  console.log(a - b);
}

执行npx webpack,控制台打印出3。但是我们去main.js里面发现我们没有import的minus方法也打包了。这是没有必要的,因为我们的业务代码只引入了add方法,将minus也打包会导致打包后的main.js过大。最理想的打包方式就是我们引入什么就打包什么。解决办法:

webpack在2.0版本以后提出了Tree shaking这个概念:实际上就是把一个模块里没用的东西都摇晃(shaking表示摇晃)掉。一个模块可以理解成一个树,比如说math.js文件是一个模块,里面会导出很多的内容,这些内容可以理解成一个小的树形结构。而我们在index.js中只引入树的一部分,只需要把引入的那一部分打包即可。

注意: Tree-shaking只支持ES module模块引入的方式引入。

配置Webpack.config.js:

mode设置成 'development'的时候是没有Tree shanking这个功能的,所以我们需要自己配置:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const webpack = require('webpack');
 
module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  entry: {
    main: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    port: 10000,
    hot: true
  },
  module: {
    rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader:'babel-loader',
    },
    {
      test: /\.(jpg|png|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 10240
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  optimization: {    //在开发环境中配置Tree shaking
    usedExports: true    //意思就是去打包引入的模块
  },
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}

package.json:

小知识:


image.png
{
  "name": "webpackDemo",
  "sideEffects": false,
  "version": "1.0.0",
  "description": "",
  "main": "postcss.config.js",
  "dependencies": {
    "@babel/polyfill": "^7.4.4",
    "@babel/runtime-corejs2": "^7.5.4",
    "autoprefixer": "^9.6.1",
    "clean-webpack-plugin": "^3.0.0",
    "core-js": "^3.1.4",
    "css-loader": "^3.0.0",
    "file-loader": "^4.0.0",
    "html-webpack-plugin": "^3.2.0",
    "node-sass": "^4.12.0",
    "postcss-loader": "^3.0.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "url-loader": "^2.0.1",
    "webpack": "^4.35.3",
    "webpack-cli": "^3.3.5",
    "webpack-dev-server": "^3.7.2"
  },
  "private": true,
  "devDependencies": {
    "@babel/core": "^7.5.4",
    "@babel/plugin-transform-runtime": "^7.5.0",
    "@babel/preset-env": "^7.5.4",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.6"
  },
  "scripts": {
    "start": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

"sideEffects": false, 意思是比如index.js中引入了babel/polyfill文件,由于它没有导出任何东西,用了Tree-shaking后导致打包会忽略它。因为当前index.js中没有引入像babel/polyfill这样的包,所以设置成false。表示没有需要特殊处理的东西。

重新执行打包,发现还是把minus给打包了,这是因为在开发环境中生成的代码需要做一些调试,如果Tree-shaking把一些代码删除掉的话

,在做调试的时候,代码对应的sourcemap的行数就都错了。所以Tree-shaking还是会保留这些代码。不过从下图可以看出,它已经提示我们了导出的只用了add这个模块。

image.png

production环境下:此时Tree-shaking就会生效了。配置如下:

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const webpack = require('webpack');
 
module.exports = {
  mode: 'production',
  devtool: 'cheap-module-source-map',
  entry: {
    main: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    port: 10000,
    hot: true
  },
  module: {
    rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader:'babel-loader',
    },
    {
      test: /\.(jpg|png|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 10240
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  //该模式下Tree-shaking的配置自动就会写好了,我们不用自己配webpack.config.js
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}

再次执行打包后发现Tree-shaking就生效了:


image.png

二. Develoment 和 Production 模式的区分打包

1、当我们项目开发完毕,就需要使用 production模式打包上线。两种打包模式的差异:

  • 开发环境中sourcemap是非常全的,这样的话再开发环境下能够帮我们快速的定位问题。production模式下,由于已经要上线了,此时sourcemap就不是那么重要了,此时的sourcemap会更加简洁一些或者我们通过生成一个.map文件来进行存储。
  • 开发环境下我们的代码不需要进行压缩,方便我们查看打包后的代码。一旦代码要上线,此时我们就希望代码被压缩,所以production模式下代码是被压缩过的。

2、我们切换两种模式的时候,需要经常修改webpack.config.js文件,这样很麻烦。解决办法:

  • 首先将 development 模式下的 webpack 配置文件命名为 webpack.dev.js 。
    在根目录下新建一个 webpack.prod.js 文件,里面放置我们线上环境下的webpack配置代码。

webpack.dev.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 
 
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    contentBase: './dist',
    port: 10000
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }, {
      test: /\.jpg$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: ['style-loader', 'css-loader', 'postcss-loader']
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  optimization: {    //在开发环境中配置Tree shaking
    usedExports: true    //意思就是去打包引入的模块
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  }
}

webpack.prod.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 
 
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  devtool: 'cheap-module-source-map',
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }, {
      test: /\.jpg$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: ['style-loader', 'css-loader', 'postcss-loader']
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  }
}

package.json:


image.png

dev: 如果要启动devserver进行开发的话,那么就是使用开发环境的webpack配置

build: 如果打包线上文件的话,就使用webpack.prod.js这个配置

此时运行npm run dev,进行的是开发环境的webpack打包配置。运行npm run build,执行的是生产环境的打包配置。

3、上面的两种打包方案的文件中存在大量的重复代码,优化方案:

  • 根目录下创建webpack.common.js文件。
  • 将两者共有的代码都赋值到该文件下,并将之前两个文件共有的代码删除。
  • 引入一个第三方模块: cnpm install webpack-merge -D,帮助我们将common的代码和两个打包文件代码做一个融合。
    webpack.common.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 
 
module.exports = {
  entry: './src/index.js',
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }, {
      test: /\.jpg$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: ['style-loader', 'css-loader', 'postcss-loader']
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  }
}

webpack.dev.js:

const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
 
const devConfig = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    contentBase: './dist',
    port: 10000
  },
  optimization: {    //在开发环境中配置Tree shaking
    usedExports: true    //意思就是去打包引入的模块
  }
}
 
module.exports = merge(commonConfig, devConfig);   //将自有的和公共的做一个结合

webpack.prod.js:

const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
 
const prodConfig = {
  mode: 'production',
  devtool: 'cheap-module-source-map'
}
 
module.exports = merge(commonConfig, prodConfig);

此时能够正常进行两种不同方案的打包。

注意:如果将三个文件都放在一个文件夹下面,此时package.json文件的命令路径需要修改,比如在build文件夹下:

"dev": "webpack-dev-server --config ./build/webpack.dev.js"

此时应该修改output配置:


image.png

三. Webpack 和 Code Splitting

Code Splitting:指的是代码分割。

此时的目录结构:


image.png

因为我们希望查看打包后的文件代码,所以不能用npm run dev来打包,添加命令:


image.png

1、例子

  • 安装一个插件:cnpm install lodash --save 它是一个功能集合,提供很多方法,可以高性能地去帮助我们使用字符串拼接的一些函数等。
    index.js添加代码:
import _ from 'lodash';
 
console.log(_.join(['a', 'b', 'c']));   //a,b,c

打包后打印出a,b,c。

假如我们index.js的业务逻辑很多,打包的话会把工具库和业务逻辑都打包到main.js文件中。此时虽然也能正常运行,但是main.js会比较大,导致加载时间很长。还有就是假如我们修改了业务代码,用户要重新去加载main.js,此时又要等很长的时间,这样就导致用户体验很差。

解决办法:

  • src目录下新建一个lodash.js文件,添加代码:
import _ from 'lodash';
 
window._ = _;  //加载了一个lodash,又将lodash挂载到了全局的下划线上面,这样的话我们在其它地方就可以是使用下划线这个变量了。

webpack.common.js添加配置:

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

再次打包,运行正常。
此时打包后分成了两个js文件,这带来的好处就是当我们修改了业务代码,用户只需要加载打包后的业务js代码即可,不用加载库的代码。

在项目中对代码公用部分进行拆分来提升项目运行的速度,也就是Code Splitting。

2、上面的例子我们是自己做的公用代码拆分,它不够智能。webpack通过它自带的一些插件可以智能地帮助我们做一些拆分工作。

  • 删除lodash.js文件,修改index.js文件:
import _ from 'lodash';
console.log(_.join(['a', 'b', 'c']));   //a,b,c
  • 添加weback.common.js配置:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 
 
module.exports = {
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }, {
      test: /\.jpg$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: ['style-loader', 'css-loader', 'postcss-loader']
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  optimization: {
    splitChunks: {
      chunks: 'all'   //意思就是我要帮你去做代码分割了
    }
  },
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].js'
  }
}

此时执行npm run dev-build打包命令,发现dist文件夹下面打包了两个js文件,一个是业务逻辑js,还有一个是类库js。

image.png

这种代码分割配置适合上面这种同步模块的分割。

3、webpack中的代码分割不仅仅适合上面这种同步模块的分割。异步的代码也能进行分割,而且不需要添加上面的那种配置就会自动进行。

index.js:

function getComponent() {
  return import('lodash').then(( { default: _ } ) => {
    var element = document.createElement('div');
    element.innerHTML = _.join(['z', 't'], '-');
    return element;
  })
};
 
getComponent().then( element => {
  document.body.appendChild(element);
});

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 
 
module.exports = {
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }, {
      test: /\.jpg$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: ['style-loader', 'css-loader', 'postcss-loader']
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  // optimization: {
  //   splitChunks: {
  //     chunks: 'all'   //意思就是我要帮你去做代码分割了
  //   }
  // },
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].js'
  }
}

执行打包命令,此时webpack对于这种异步的代码也能智能地进行分割。


四. SplitChunksPlugin 配置参数详解
webpack的代码分割底层使用了SplitChunksPlugin这个插件。

上面打包之后的dist文件夹:

image.png

1、 我们希望把 0.js 改一个名字

修改index.js文件:

function getComponent() {
  //下面的意思就是异步地引入lodash这样一个库,当我做代码分割的时候,单独给lodash进行打包的时候,给它起个名字叫lodash
  return import(/* webpackChunkName: "lodash" */'lodash').then(( { default: _ } ) => {
    var element = document.createElement('div');
    element.innerHTML = _.join(['z', 't'], '-');
    return element;
  })
};
 
getComponent().then( element => {
  document.body.appendChild(element);
});
  • 安装一个插件帮我们识别上面那种魔法字符串写法:cnpm install --save-dev @babel/plugin-syntax-dynamic-import
  • .babelrc添加配置:
{
  "presets":[
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage"
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": ["@babel/plugin-syntax-dynamic-import"] 
}

执行 npm run dev-build 命令后,


image.png
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 
 
module.exports = {
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }, {
      test: /\.jpg$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: ['style-loader', 'css-loader', 'postcss-loader']
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  optimization: {
    splitChunks: {
      chunks: 'all',   //意思就是我要帮你去做代码分割了
      cacheGroups: {
        vendors: false,
        default: false
      }
    }
  },
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].js'
  }
}

执行打包命令后,dist文件夹下:


image.png

2、SplitChunks配置介绍:

splitChunks: {
    chunks: "async",  //在做代码分割的时候,只对异步代码生效。
    minSize: 30000,   //发现引入的块大于30000个字节的话,就会帮我们做代码分割
    minChunks: 1,     //当一个Chunk被至少用了多少次的时候,才对他进行代码分割
    maxAsyncRequests: 5,  //同时加载的模块数,最多是5个
    maxInitialRequests: 3,  //整个网站首页进行加载的时候,或者说入口文件进行加载的时候,入口文件可能会引入其它的js文件,入口文件如果做代码分割,最多只能分割出3个。此处一般不修改
    automaticNameDelimiter: '~',  //组和文件名之间连接的符号
    name: true,   //打包生成的名字通过cacheGroup设置有效。此处一般不变
    cacheGroups: {  //如果都符合下面俩个组的要求,那么谁的priority值大,就用谁的
        vendors: {  //vendors组
            test: /[\\/]node_modules[\\/]/,   //如果是从node_modules引入的
            priority: -10
        },
    default: {      //这个组里面没有test,意思就是所有的模块都符合要求
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true  //如果一个模块已经被打包了,再打包就会忽略这个模块,直接使用之前使用的那么模块就可以
        }
    }
}

对同步的代码进行分割:

splitChunks: {
    chunks: "all",   //1  同步和异步代码都会做分割,initial表示对同步代码做分割
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    name: true,
    cacheGroups: {
        vendors: {  //2
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
            filename: 'vendors.js'    //打包后的名字不带main,只有vendors
        },
        default: false   
    }
}

五. Lazy Loading 懒加载,Chunk 是什么?

1、实现一个懒加载的行为

index.js:

function getComponent() {
  return import(/* webpackChunkName: "lodash" */'lodash').then(( { default: _ } ) => {
    var element = document.createElement('div');
    element.innerHTML = _.join(['z', 't'], '-');
    return element;
  })
};
 
document.addEventListener('click', () => {
  getComponent().then( element => {
    document.body.appendChild(element);
  });
})

webpack.common.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 
 
module.exports = {
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }, {
      test: /\.jpg$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: ['style-loader', 'css-loader', 'postcss-loader']
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  optimization: {
    splitChunks: {
      chunks: 'all',   //意思就是我要帮你去做代码分割了
      cacheGroups: {
        vendors: false,
        default: false
      }
    }
  },
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].js'
  }
}

执行打包命令,此时控制台的network只加载了main.js:


image.png

再页面点击之后,才加载lodash.js文件:

image.png

上面这种做法的好处是能够让页面的加载速度更快,只加载该页面用到的js代码。
可以使用async函数来实现上面的效果,index.js:

async function getComponent() {
  const { default: _ } = await import(/* webpackChunkName: "lodash" */'lodash');
  const element = document.createElement('div');
  element.innerHTML = _.join(['z', 't'], '-');
  return element;
}
 
document.addEventListener('click', () => {
  getComponent().then( element => {
    document.body.appendChild(element);
  });
})

2、Chunk

Webpack打包过后生成了几个js文件,每个js文件我们都把它叫做一个Chunk。


六. 打包分析,Preloading, Prefetching
1、打包分析:当我们使用Webpack进行打包之后,我们可以借助打包分析的工具来对我们的打包文件进行一定的分析,然后看打包是否合理。

打包分析网址
https://github.com/webpack/analyse

上面网址中的教程:

运行:npx webpack --profile --json > stats.json 会在根目录下生成一个stats.json,该文件就是对打包过程的一个描述。
生成该文件以后,我们就可以借助一些工具来分析里面的内容了:进入上面的网址,点击首页的链接,就会进入一个页面,进入之后将stats.json文件上传上去就会帮我们进行打包分析了。
除了这个分析方法之外,还有很多别的方法,可以去webpack的文档中查看。


七. CSS 文件的代码分割
index.js

import './style.css';
  • 在src目录下创建style.css文件,添加代码:
body {
  background-color: green;
}

执行打包后发现虽然dist目录下没有css文件,但是页面却又效果。这是因为webpack在做打包的时候会把Css文件直接打包到js里面。

1、希望实现Css文件单独打包进dist文件夹下面。

借助 MiniCssExtractPlugin 这个插件来进来css代码分割。

  • 安装:
npm install --save-dev mini-css-extract-plugin
  • webpack.common.js添加配置:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 
module.exports = {
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }, {
      test: /\.jpg$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          limit: 2048
        }
      }
    }, {
      test: /\.scss$/,
      use: [
        MiniCssExtractPlugin.loader,    //1
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']   //2
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({})
  ],
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].js'
  }
}

此时执行打包命令后发现dist目录下成功打包出了css文件。

2、做Css代码的压缩

const TerserJSPlugin = require('terser-webpack-plugin');
// 安装这两个插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
 
module.exports = {
  // 配置压缩
  optimization: {
    minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

今天回顾下webpack的相关知识点,发现了这篇文章写的相当详细,顺手摘过来记录下,整体看下来的大概1个小时左右,备份下
原文链接:https://blog.csdn.net/sinat_40697723/article/details/95349783

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269