# 1. WebPack 基础知识
首先需要明白webpack
为什么会出现,作用是什么,总之他就是一个模块打包工具;
commonJS
的模块导出方式为:
function SiderBar() {
var dom = document.getElementById('root');
var siderbar = document.createElement('div');
siderbar.innerHTML = 'siderbar';
dom.append(siderbar);
}
module.exports = SiderBar
commonJS
的模块导入(引入)方式为:
var SiderBar = require('./siderbar.js')
webpack
是一个打包工具,,他能识别任何一个模块引入的语法,包括ES Module(也就是 import export)、CommonJS 、CMD 、AMD
都可以识别。webpack
都能够进行很好的翻译。
# 2. Webpack 安装、使用配置文件、浅析打包输出的内容、Loader、使用 Loader 打包静态资源(图片篇)、使用 Loader 打包静态资源(样式篇)
# 2.1 安装有两种方式:全局安装与局部安装
- 全局安装:
npm install webpack webpack-cli -g
不推荐使用,卸载npm uninstall webpack webpack-cli -g
; - 局部安装:
npm install webpack webpack-cli --save-dev
或者npm install webpack webpack-cli -D
是等价的。局部安装的直接输入webpack -v
是不能运行这个命令的,可以通过npx webpack -v
去运行命令。npx
会从项目的node_modul
去找wenpack
包,所以可以通过npx
去查找我们在项目中的webpack
包;可以在不同的项目中使用不同版本的webpack
; 输入npm info webpack
可以查看包的信息,包括版本等信息。 - 安装指定版本的
webpack
:npm install webapck@版本号 webpack-cli -D
# 2.2 使用配置文件
webpack
在进行打包的时候,首先会查找项目中是否存在配置文件webpack.config.js
,如果没有,他会使用默认的配置文件进行打包。
在项目的目录下新建webpack.config.js
文件,里面输入内容:
// 引入node核心模块path
const path = require('path')
module.exports = {
// 入口文件
entry: './src/index.js',
// 打包出的文件配置
output: {
// 文件名
filename: 'bundle.js',
// 打包后的文件放在哪个文件夹,是一个绝对路径
// __dirname就是webpack.config.js所在的当前目录的路径,改成bundle就是说,打包后的文件放在bundle文件夹中
path: path.resolve(__dirname,'bundle')
}
}
代码的注释很详细,就不在重复;上面代码就是说将我们项目中的/src/index.js
文件进行打包到bundle
文件夹下的bundle.js
中。
一般情况,我们将我们的源代码放在src
目录下。
需要注意的是:
webpack
中默认的配置文件是webpack.config.js
,我们可以输入命令npx webpack --config webpackconfig.js
将默认的配置文件指向webpackconfig.js
这个文件。
我们每次进行打包的时候,都需要输入命令npm webpack
,我们可以在webpack.config.js
这个文件中进行配置我们允许的命令;如下面代码:
"scripts": {
"bundle": "webpack"
},
上面的配置就是当我们输入npm run build
的时候相当于输入 npm webpack
,进行打包我们的文件。这里运行 npm webpack
是不会进行全局检测是否安装了 webpack
,而是从项目的node_module
去查找。
补充:查看官网的
guides
中的Getting started
里面的内容
# 2.3 浅析打包输出的内容
我们输入命令npm run bundle
或者npx webpack
之后,控制台会输出下面的内容:
Hash: 7d4f5a28d798a39e7e58
Version: webpack 4.31.0
Time: 293ms
Built at: 2019-05-16 08:56:10
Asset Size Chunks Chunk Names
bundle.js 1.36 KiB 0 [emitted] main
Entrypoint main = bundle.js
[0] ./src/index.js 870 bytes {0} [built]
[1] ./src/header.js 249 bytes {0} [built]
[2] ./src/siderbar.js 246 bytes {0} [built]
[3] ./src/content.js 240 bytes {0} [built]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
Hash
:是对应打包成功后文件的Hash
值是唯一值;
bundle.js
:我们打包出的文件;
Chunks
:我们打包后的文件,每一个文件都会有一个自己的id
值,这里面存放该文件的id
值跟与其他打包后的文件有关系的对应文件的id
值;
Chunk Names
:存放Chunks
中每一个id
值对应的名字。这里的main
,这里的入口文件,对应的名字就是main
;我们前面说到配置打包的入口文件:
// 入口文件
entry: './src/index.js',
其实是下面的简写形式:
// 入口文件
entry: {
main: './src/index.js',
},
WARNING in configuration
:这里的警告说,我们在进行打包的时候,没有配置打包的模式与打包的环境;其实我们在没有配置模式的情况下默认是production
,如下面的代码:
// 引入node核心模块path
const path = require('path')
module.exports = {
// 配置打包模式
mode: 'production',
// 入口文件
entry: {
main: './src/index.js',
},
// 打包出的文件配置
output: {
// 文件名
filename: 'bundle.js',
// 打包后的文件放在哪个文件夹,是一个绝对路径
// __dirname就是webpack.config.js所在的当前目录的路径,当前模块的目录名,改成bundle就是说,打包后的文件放在dist文件夹中
path: path.resolve(__dirname,'dist')
}
}
添加之后,再次进行打包就不会出现警告了:
Hash: 81601a0beb210cf86adc
Version: webpack 4.31.0
Time: 632ms
Built at: 2019-05-16 15:20:51
Asset Size Chunks Chunk Names
bundle.js 1.36 KiB 0 [emitted] main
Entrypoint main = bundle.js
[0] ./src/index.js 870 bytes {0} [built]
[1] ./src/header.js 249 bytes {0} [built]
[2] ./src/siderbar.js 246 bytes {0} [built]
[3] ./src/content.js 240 bytes {0} [built]
这个production
模式打包后的文件,会对文件进行压缩,上面打包后的文件内容如下:打包为一行;
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=0)}([function(e,n,t){var r=t(1),o=t(2),u=t(3);new r,new o,new u},function(e,n){e.exports=function(){var e=document.getElementById("root"),n=document.createElement("div");n.innerHTML="header",e.append(n)}},function(e,n){e.exports=function(){var e=document.getElementById("root"),n=document.createElement("div");n.innerHTML="SiderBar",e.append(n)}},function(e,n){e.exports=function(){var e=document.getElementById("root"),n=document.createElement("div");n.innerHTML="content",e.append(n)}}]);
我们将打包模式配置成mode: 'development'
;打包后的内容就不会进行压缩,内容如下:
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
# 2.4 Loader解析
webpack
本身只是识别js
文件的打包,当我们需要进行打包比如图片等文件时,需要在配置文件里面进行配置;webpack
在进行打包的时候,首先是直接打包js
文件,然后其他的文件会通过配置文件里面的module
里面进行查找对应文件的打包规则;如果我们需要对图片文件进行打包,配置如下:
module: {
rules: [{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader'
}
}]
},
然后记得进行安装对应的loader
;输入命令:npm install file-loader --save-dev
然后运行打包命令就可以了;
loader
就是一个打包方案,只要不是.js
文件,就需要进行配置打包的规则。
# 2.5 使用 Loader 打包静态资源(图片篇)、url-loader的使用
如果我们在打包静态资源的时候,比如图片,默认打包后的结果是会生成一个字符串为图片命名,我们现在希望我们打包之后他的文件名以及扩展名,也就是文件类型都是不会改变,可以进行如下配置:[name].[ext]
分别代表的是之前文件的文件名以及之前文件的后缀,他其实就是一个webpack
的一个占位符。可以在官网的file-loader
里面可以看到很多占位符的用法。
module: {
rules: [{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
// 占位符 Placeholders
name: '[name].[ext]'
}
}
}]
},
同时我们可以设置打包后输出的的文件夹的位置:下面是将图片文件打包在images
文件夹中。可以查看官网的文档里面file-loader
的相关配置。
module: {
rules: [{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/'
}
}
}]
},
# url-loader 的使用:
url-loader
可以进行上面的打包,但是唯一不同的是,url-loader
是将图片直接打包成一个base:64
的字符串,进行显示在src
中。
这个会有一些问题,是减少了发送http
请求,但是如果文件很大,加载的时间也是很长,页面会显示空白。如果图片小,可以使用这种方式进行打包。如果图片很大,需要将图片通过file-loader
打包到一个文件夹,这样更适合项目。其实在url-loader
中已经考虑到了,我们可以进行添加一个配置进行限制:limit: 2048
意思就是如果图片超过2048个字节,也就是2kb
就将图片打包到imagees
文件夹中,如果小于,就直接生成一个base:64
的字符串进行显示。
module: {
rules: [{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
}]
},
可以查看官网API
进行深入的研究这两个loader
;
# 2.6 使用 Loader 打包静态资源(样式篇)
首先需要进行配置打包css
文件的打包配置,需要使用style-loader、css-loader
两个loader
进行打包。其中css-loader
他是可以解析我们css
文件之间的依赖关系,比如我们的index.css
中引用了avatar.css
文件,如下:@import './avatar.css';
他就会进行解析,之间的依赖关系,最终将这些文件打包到一个文件中。style-loader
的作用是,当得到css-loader
打包后的内容之后,会将这些挂载到页面的header部分
,也就是放在了style
标签中。配置如下:
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
},
如果我们需要打包.sass、scss
这些其他css
文件,我们需要引入sass-loader
进行打包:npm install sass-loader node-sass webpack --save-dev
如下的配置:
module: {
rules: [ {
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}]
},
其实loader
的执行是有先后顺序的:从上到下、从右到左;比如上面的配置,首先是通过sass-loader
进行处理,然后通过css-loader
进行处理文件关系,最后通过style-loader
进行挂载到页面。
# 2.6.1 对于 css 打包进行添加厂商前缀,使用postcss-loader
自动添加厂商前缀:
首先进行安装npm i -D postcss-loader
;然后新建postcss.config.js
文件,然后进行安装一个autoprefixer
插件,输入命令:npm install autoprefixer -D
。postcss.config.js
文件配置如下:
module.exports = {
plugins: [
require('autoprefixer')
]
}
最后需要记得在打包配置里面进行添加postcss-loader
配置项:
module: {
rules: [{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
'postcss-loader'
]
}]
},
其实我们的厂商前缀是由postcss-loader
里面我们添加的autoprefixer
插件给我们添加的厂商前缀。
# 2.6.2 css-loader 中一些常用的配置项:
我们在进行配置loader
的时候,如果里面需要配置参数,那么将loader
写成一个对象,而不是数组里面的字符串,我们给css-loader
添加一个选项:importLoaders: 2
,如下面代码:importLoaders: 2
参数的意思是:如果我们在js
文件里面通过import
语句进行引入scss
文件是可以进行打包的,按照我们前面说的,从下往上执行loader
,但是如果我们的在js
文件中引入的scss
文件中还通过import
引入了其他scss
文件,那么这里的scss
文件在进行打包的时候不会进行类似在js
文件中scss
打包的过程从下往上执行loader
,所以我们需要添加配置项importLoaders: 2
,意思就是我们在打包scss
文件的时候(或者css
)需要执行前面两个loader
,这样就保证了不管scss
文件(或者css
)中通过import
引入多少其他的scss
文件(或者css
),都能够正常的打包,都会依次从上到下依次执行loader
进行打包。
module: {
rules: [{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}]
},
# 2.6.3 css 文件模块化打包:只在一个模块内有效
我们在写css、scss
的时候会直接将他们在代码中进行引入,如下:这会导致如果页面创建元素,所有的元素的样式都会受到影响,
import './index.scss';
import avater from './avater.png';
import createAvatar from './createAvatar'
createAvatar();
// 打包图片文件--avater为打包后的文件名
var img = new Image();
img.src = avater;
img.classList.add(avater)
var dom = document.getElementById('root');
dom.append(img);
在createAvatar.js
文件中也是创建一个img
标签:
import avater from './avater.png'
function createAvatar () {
var img = new Image();
img.src = avater;
img.classList.add('avater')
var dom = document.getElementById('root');
dom.append(img);
}
export default createAvatar
在index.scss
中,代码如下:
body {
.avater {
height: 150px;
width: 150px;
transform: translate(100px, 100px);
}
}
这样写会导致页面中两个图片显示都一样,都使用了index.scss
中的样式;解决这个问题,就是我们可以在打包css
这些文件的时候,实现模块化打包,如下面代码:
在打包的时候,进行配置打包为模块;modules: true
意思就是开启模块化打包。
module.exports = {
// 配置打包模式
mode: 'development',
// 入口文件
entry: {
main: './src/index.js',
},
module: {
rules: [{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: true
}
},
'sass-loader',
'postcss-loader'
]
}]
},
然后在代码中通过style.avater
这种方法,进行添加样式,这样就不影响全部文件了。
import style from './index.scss';
import avater from './avater.png';
import createAvatar from './createAvatar'
createAvatar();
// 打包图片文件--avater为打包后的文件名
var img = new Image();
img.src = avater;
img.classList.add(style.avater)
var dom = document.getElementById('root');
dom.append(img);
# 2.6.4 打包字体文件:
我们项目里面也经常会用到图片库中的小图片,比如iconfont
里面的小图片,我们查看iconfont.css
发现,里面引入了eot|ttf|svg|woff
这些文件,如下面代码:
@font-face {
font-family: "iconfont";
src: url('./font/iconfont.eot?t=1558162704401'); /* IE9 */
src: url('./font/iconfont.eot?t=1558162704401#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAASIAAsAAAAACQgAAAQ7AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCCfgqFVIRuATYCJAMMCwgABCAFhG0HWxvRBxHVoy2Q/SxwzuYE2hXq6ImKdq351ClK774P+U+55ftJYCczKxZYca2qhqqHlK24nUQMelK9FgLpdvvtgwTDKPAeOIriDMIYEisANjdD9/zrCMWSnOf9tMy0dKYX0l//czm9awvOD5TLXMuOegGGvYACGmPbAgq0QBL0luFVB7kbx4EAXIwsNBq16ABGoY8jAN1DB/cFS+goFcWEIdaCpQV60LG2WLoDYKX3ffpREAyQWBL61DaDGg5Anff0XpJ1r+50EPD7swBgVwEyIAugAB2FtjaQg1gWGZdtiqTqSgXBe8nrdXDEm+0fHkAkoAm6zBcT0+ISvCeLv2t8jAN/31cBVgAD/KeVEEN3tJC34tqkxgXGTXfu3rlOc+Hr8MTcgtOSZ+3EUtPhtPGde0E3oi52evhU0NLOz59jd2fHqMcOJzH9aVu+ZJ1uCLHUbjdt5HAiDzx61pbXhwd6tFHySNO01ZCj8/OxS00ix/7dJ0PLqOZyCt95MQyuS8liz5VIqjb2mDW2dlPIXA/rQrkT1MMuDA2uc6mtTW+mbvTjrn1LDzj2L9k7nXc2x/GEdvDtNJpV1zp2gypowHGWmss91q3j/kucZHGY9gDD0AZ06pO7BjY2dOwwyZt01y77UnvnPY7KoDOqqHeEt4hBq+jG0Q2HDWvo3qU1Wiwqad9EuDX31jEYFDiWezSPWuu6WdkBXdvGt4yfOiXOrmmLLgk3fnYYtWktF5e2b/3+HjE28ELhIt/x3G1z8/Kr68rbo2ub+FZxU6bGtUxo2zWhbvgrVVl9RW0Z2zqmcVRDu79wi7pB4of00ieV0apXehC7x4L0V6uIUZ1JeW/lSTsn1Ss7KmKZ/kpX76nOnVhdS//Izv3GW+tyT3G/NHRKGJQwMKEjSpfDB3r3Ghz6PXTSnDnclEwKxJNTvXY8GOzXzW/Qg3cPJnmPmsy2+BxQ37ELrJt6fsw7fT/z45fmG22bHfdtLL5pblGya8Ku8ZWrLf/OW/gez1jlt7tslshAHwCA91KekTNJ4nqek8v/ld8g8P38sHdA1T8mFADAS8u9QOzxXirAHAC7m3aCP4MPrFMUoa6XS+JZPC0pO4x+77XgihNe6K5gnvYKDVwIEEaLQOIjGmRMEqoQs8BCKAIrpjZwmRqvFkKMg4xKBZBhPgIS6AhI/F0BWaC7qEJ0g4XNN7AKDBm4ThSypZASuFU74yK4w/oDu1HSplGUX3xDU8LCcaFHfSGfNghd3aaTV0zIfcw436YX0aCZIozoNAyBIDM53KQ+RPLQNLroRfVGsVLqMy6CO1h/wG6UtNNcVObzNzQlLFyBKbO+kE/bOejUWgB6lSUQ5lx6n2/TE9HUKM0UwYgtGIwYgVw8yOEmtaNFJA8a0khDZfX4inh922CmT81okVHCriWZ/1FMit0iU0QrfosS7CXX8isp8K5UVQA=') format('woff2'),
url('./font/iconfont.woff?t=1558162704401') format('woff'),
url('./font/iconfont.ttf?t=1558162704401') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('./font/iconfont.svg?t=1558162704401#iconfont') format('svg'); /* iOS 4.1- */
}
我们如果直接使用webpack
打包会提示报错,无法打包那些后缀的文件,我们可以通过file-loader
进行打包这些字体文件,在webpack.config.js
里面进行配置,如下:使得将字体文件打包到dist
的相对路径里面,我们直接使用。
module: {
rules: [ {
test: /\.(eot|ttf|svg|woff)$/,
use: {
loader: 'file-loader'
}
}]
},
然后我们可以继续深入,查看官网的
document
中的GUIDS
里面的Asset Management
里面的内容,还有loader
里面的sass-loader、css-loader、postcss-loader
进行深入了解。
# 3. 使用Plugins让打包更便捷
# 3. 1 html-webpack-plugin 插件的使用——将html进行打包,并根据设置的html模板进行打包
我们在打包的时候,每次打包之后,打包后的文件,并没有index.html
就是我们在src
文件夹里面写的网页,都需要我们进行复制到dist
目录里面,很是麻烦,我们可以使用webpack
插件,来帮助我们解决这个问题。
首先需要安装这个插件:cnpm install --save-dev html-webpack-plugin
,然后在webpack.config.js
里面进行配置,代码如下:
const HtmlWebpakcPlugin = require('html-webpack-plugin')
module.exports = {
// 配置打包模式
mode: 'development',
// 入口文件
entry: {
main: './src/index.js',
},
plugins: [new HtmlWebpakcPlugin()],
// 打包出的文件配置
output: {
// 文件名
filename: 'bundle.js',
// 打包后的文件放在哪个文件夹,是一个绝对路径
// __dirname就是webpack.config.js所在的当前目录的路径,当前模块的目录名,改成bundle就是说,打包后的文件放在dist文件夹中
path: path.resolve(__dirname, 'dist')
}
}
这样,打包之后,会将我们的html
文件也会进行打包,html-webpack-plugin
这个插件在打包结束后,自动生成一个html
文件,并把打包生成的js
自动引入到这个html
中。我们打开打包后的html
文件,会发现,我们在里面的html
结构没有显示,比如我们在里面写的<div></div>
没有被打包显示,这个时候,我们需要对html-webpack-plugin
这个插件进行配置一个template
选项,让他根据模板进行打包我们的html
文件,模板文件也就是我们需要打包的文件,也就是以模板文件进行打包。配置如下:
const HtmlWebpakcPlugin = require('html-webpack-plugin')
module.exports = {
// 配置打包模式
mode: 'development',
// 入口文件
entry: {
main: './src/index.js',
},
plugins: [new HtmlWebpakcPlugin({
template: './src/index.html'
})],
// 打包出的文件配置
output: {
// 文件名
filename: 'bundle.js',
// 打包后的文件放在哪个文件夹,是一个绝对路径
// __dirname就是webpack.config.js所在的当前目录的路径,当前模块的目录名,改成bundle就是说,打包后的文件放在dist文件夹中
path: path.resolve(__dirname, 'dist')
}
}
Plugin
可以在webpack
运行到某一时刻的时候,帮你做一些事情,很类似vue
的声明周期函数;
# 3.2 clean-webpack-plugin 插件的使用——清除打包地址的文件
我们在查看我们打包后的内容,我们如果将output
配置里面的输出文件名修改为其他的,我们重新进行打包,会发现,打包是成功了,但是我们修改之前打包的js
文件还是存在在dist
文件夹中,我们需要做的是,每次打包之前,需要将dist
中之前打包的文件先进行删除,然后再次进行打包js
文件。这个时候,我们需要引入一个插件clean-webpack-plugin
,
首先输入命令进行安装:npm install clean-webpack-plugin -D
然后在webpack.config.js
里面进行配置,如下:new CleanWebpackPlugin()
意思是每次打包之前删除dist
里面所有的内容。
// 引入node核心模块path
const path = require('path')
// 将我们写的html文件,进行打包;
const HtmlWebpakcPlugin = require('html-webpack-plugin')
// 清除上次打包生成的js文件
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
// 配置打包模式
mode: 'development',
// 入口文件
entry: {
main: './src/index.js',
},
plugins: [new HtmlWebpakcPlugin({
template: './src/index.html'
}),new CleanWebpackPlugin()],
// 打包出的文件配置
output: {
// 文件名
filename: 'bundle.js',
// 打包后的文件放在哪个文件夹,是一个绝对路径
// __dirname就是webpack.config.js所在的当前目录的路径,当前模块的目录名,改成bundle就是说,打包后的文件放在dist文件夹中
path: path.resolve(__dirname, 'dist')
}
}
这里需要注意的是
HtmlWebpakcPlugin
是在打包之后运行的插件,CleanWebpackPlugin
是在打包之前运行的插件。还有一个需要注意的是new CleanWebpackPlugin(['dist'])
这里的dist
是不需要添加的,直接new CleanWebpackPlugin()
就好了
# 4. Entry 与 Output 的基础配置
# 4.1 多个文件的打包
我们在打包文件的时候,经常会遇到打包多个文件,而不是一个js
文件,这时候我们需要修改我们的配置;比如,下面代码,我们打包main
跟sub
的文件:
// 入口文件
entry: {
main: './src/index.js',
sub: './src/index.js'
},
如果我们还是用之前的打包输出的配置;就会报错,提示多个文件打包到一个文件的错误:
// 打包出的文件配置
output: {
// 文件名
filename: 'main.js',
// 打包后的文件放在哪个文件夹,是一个绝对路径
// __dirname就是webpack.config.js所在的当前目录的路径,当前模块的目录名,改成bundle就是说,打包后的文件放在dist文件夹中
path: path.resolve(__dirname, 'dist')
}
这个时候我们需要配置输出的文件,让他根据入口配置的文件,也就是那个对象的键,根据键来进行打包输出文件,配置如下:
// 打包出的文件配置
output: {
// 文件名
filename: '[name].js',
// 打包后的文件放在哪个文件夹,是一个绝对路径
// __dirname就是webpack.config.js所在的当前目录的路径,当前模块的目录名,改成bundle就是说,打包后的文件放在dist文件夹中
path: path.resolve(__dirname, 'dist')
}
这样,打包后输出的文件分别为main.js
跟sub.js
;这样就完成了多个文件的打包。
# 4.2 将打包后的js文件在html中,通过配置cdn地址进行引入
我们经常会遇到,我们在打包我们的js
代码之后,会将js
文件放在cdn
服务器上,通过cdn
服务器的地址来在页面进行引入脚本。比如,我们希望在html
引入js
的时候地址前面加上cdn
域名:http://cdn.com/main.js
这样。我们需要在output
里面进行配置一个publicPath
的选项:
// 打包出的文件配置
output: {
publicPath: 'http://cdn.com.cn',
// 文件名
filename: '[name].js',
// 打包后的文件放在哪个文件夹,是一个绝对路径
// __dirname就是webpack.config.js所在的当前目录的路径,当前模块的目录名,改成bundle就是说,打包后的文件放在dist文件夹中
path: path.resolve(__dirname, 'dist')
}
打包后html
的引入如下:
<script type="text/javascript" src="http://cdn.com.cn/main.js"></script>
<script type="text/javascript" src="http://cdn.com.cn/sub.js"></script>
进一步学习:查看官网的document
里的configuration
中的output
的配置参数。然后进行查看guides
里面的output management
里的内容。查看plugins
里面的htmlwebpackplugin
插件,查看该插件对应的官网。
# 5. SourceMap 的配置
我们在前面配置了我们的打包模式 为开发者模式:mode: 'development',
在这个模式,默认SourceMap
已经被配置进去了,我们可以关掉认SourceMap
,配置如下:
// 配置打包模式
mode: 'development',
devtool: 'none',
如果我们关闭了SourceMap
,我们如果在写代码的时候,不小心制造一个小bug
,比如说我们把console
写成了conssole
这样,在打包时,是不会出现问题的,但是在页面的控制台会提示,该方法不存在,我们点击之后,提示打包后的文件的第153行报错,其实我们真正 想知道的不是打包后文件哪里出错,而是我们在源代码里哪个位置出现错误;而SourceMap
他是一个映射关系,他知道dist
目录下的main.js
也就是我们打包生成的文件153行实际上对应是src
目录下index.js
文件也就是我们的源代码文件中的第58行。我们可以通过SourceMap
获取到index.js
中第一行代码出错了;所以我们需要进行配置SourceMap
,来根据SourceMap
的映射关系,找到我们源代码出错位置:我们查看打包后的文件,会多出一个main.js.map
的文件,就是我们的源文件,存放着映射关系。
// 配置打包模式
mode: 'development',
devtool: 'source-map',
我们可以查看官网有关SourceMap
的配置,也就是devtool
的配置,其中如果将devtool
配置成inline-cheap-source-map
意思他会将打包后的SourceMap
直接通过地址放在在打包的文件代码的底部:
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vc3JjL2luZGV4LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGtEQUEwQyxnQ0FBZ0M7QUFDMUU7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxnRUFBd0Qsa0JBQWtCO0FBQzFFO0FBQ0EseURBQWlELGNBQWM7QUFDL0Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQ
这里,需要说明的是,前面我们进行配置成source-map
,点击报错,他会提示代码报错位置的行与列,其实我们在查看问题的时候,并不需要精确到哪一列代码出现问题,只需要知道在哪一行;这时候我们可以将devtool
配置成cheap-source-map
,他只会提示代码在哪一行报错。
这里的cheap-source-map
他就会映射我们写的业务代码,比如打包的文件index.js
而不会将我们在loder
里面引入的第三方模块代码的映射,如果需要映射loder
里面的的代码需要将配置修改为cheap-module-inline-source-map
还有一种打包方式就是eval
,他是打包速度最快的,但是如果代码过多,也会影响打包速度;
这里建议我们配置
sourceMap
的打包方式,如果是在development
环境进行打包的时候,将devtool
配置成cheap-module-eval-source-map
,提示的错误比较全,打包速度也是比较快。如果我们代码已经上线,也就是在production
模式下,我们可以将devtool
配置成cheap-module-source-map
,这样提示效果会好一些。
# 6. 使用 WebpackDevServer 提升开发效率
我们现在每次进行打包的时候还要需要输入打包命令,打包之后在将html
打开,查看打包结果;这样操作是比较麻烦的,我们需要的是可以监听到我们改动的文件,然后自动进行重新打包,我们有三种方法进行配置;
# 6.1 第一种:我们可以在package.json进行配置,监听文件变化
"scripts": {
"watch": "webpack --watch"
},
他的意思是,会进行监听我们打包的文件,只要打包文件发生变化,就会重新打包。
# 6.2 第二种:监听打包文件变化,并自动启动服务器,加载html文件
我们的第二种打包方式是,我们不仅需要监听打包文件的变化并重新打包,而且需要在打包完成之后,直接打开我们的html
文件,类似在服务器中。前提是首先安装webpack-dev-server
,输入命令:npm install webpack-dev-server -D
。
我们可以在webpack.config.js
添加一个devServer
配置:
module.exports = {
devServer: {
// 服务器启动的根路径
contentBase: './dist'
}
}
然后在package.json
里面添加一个运行命令,来运行我们的服务器:
"scripts": {
"watch": "webpack --watch",
"start": "webpack-dev-server"
},
这时候会启动一个服务器,webpack-dev-server
比我们之前配置的webpack --watch
这种方式的好处是,他不仅会监听我们打包文件变化,并进行更新打包,而且也会重新刷新我们的网页。我们还可以在webpack.config.js
中的devServer
配置添加一个open: true
选项,他会在打开服务器后,自动打开浏览器,并自动访问启动的服务器地址,打开网页,不用我们手动进行将服务器启动后的地址输入到浏览器进行打开:
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true
},
还有一个就是我们在使用vue-cli
脚手架的时候,我们会在devServer
里面配置一个proxy
选项,用来模拟后台API
请求地址或者转发我们的接口API
地址,解决跨域代理;如下面的配置:他的意思是当我们访问localhost:8080/api
地址的时候,他会自动将地址转发到localhost:3000
端口。
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
proxy: {
'/api': 'http://localhost:3000'
}
},
# 6.3 第三种:自己手写一个服务器,实现类似Webpack-dev-server的效果
首先在package.json
里面进行配置命令,使得运行命令之后,就运行我们的脚本文件:
"scripts": {
"middleware": "node server.js"
},
我们可以借助node
的框架,去搭建服务器,这里使用express
框架来实现,首先进行安装该框架:npm install express webpack-dev-middleware -D
,我们这里同时安装了一个webapck
的webpack-dev-middleware
中间件,用来监听我们打包文件的变化,来自动进行打包。
然后我们修改webpack.config.js
里面的output
配置,添加一个publicPath: '/'
将文件打包成功后的引用地址前面都加一个根路径,确保打包生成文件的引用路径不会出现错误。
// 打包出的文件配置
output: {
// 文件引入的cnd地址
// publicPath: 'http://cdn.com.cn',
publicPath: '/',
// 文件名
filename: '[name].js',
// 打包后的文件放在哪个文件夹,是一个绝对路径
// __dirname就是webpack.config.js所在的当前目录的路径,当前模块的目录名,改成bundle就是说,打包后的文件放在dist文件夹中
path: path.resolve(__dirname, 'dist')
}
然后我们的server.js
代码如下:
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
// 进行webpack编译--执行编译器,进行打包一次代码,
// 在node中直接使用webpack
const complier = webpack(config);
const app = express();
// 只要打包的文件内容发生变化,就会重新打包,通过编译器;将打包的文件放在跟webpack.config.js配置的一致
app.use(webpackDevMiddleware(complier, {
publicPath: config.output.publicPath
}));
app.listen(3000, () => {
console.log('server is run')
})
上面的我们是直接通过调用webpack
配置在node
里面使用webpack
,我们也可以直接在命令行里去执行webpack
,可以在官网的API
里面的Command Line Interface
查找到很多的命令:比如webpack index.js -o main.js
将index.js
打包成main.js
输出。如果需要查找在node
里面运行webpack
,可以查看node.js APi
里面去查找。
深入学习:打开官网的
documentation
里面的Guides
中的development
里面的内容阅读,然后查看configuration
里面有关devtool、devServer
里面的内容。
# 7. Hot Module Replacement 热模块更新
我们使用webpack-dev-server
帮助我们进行打包,会发现不会产生一个dist
,因为他会生成一个打包文件,不过没有放在文件里面而是放在了计算机的内存中,可以有效提升打包的速度。
我们在写代码的时候,经常会碰到,我们在修改了我们的css
文件后,只是想每次在更新页面的时候,只是将样式文件重新加载,进行渲染,并不想让整个页面进行重新加载,破坏了我们页面的状态;我们想做的就是每次改完样式文件之后,只是重新加载样式文件,不破坏页面的状态;我们可以使用热模块更新来解决这个问题:我在webpack.config.js
里面的devServer
配置项中添加一个hot: true
(让webpack-dev-server
开启热模块更新的功能)选项跟hotOnly:true
(即便是HTML的功能没有生效,也不让浏览器自动重新刷新,)
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
proxy: {
'/api': 'http://localhost:3000'
},
hot: true,
hotOnly: true
},
然后我们在webpack.config.js
中引入webpack
,在plugin
里面进行添加一个webpack
的插件webpack.HotModuleReplacementPlugin()
;代码如下:
// 引入node核心模块path
const path = require('path')
// 将我们写的html文件,进行打包;
const HtmlWebpakcPlugin = require('html-webpack-plugin')
// 清除上次打包生成的js文件
const CleanWebpackPlugin = require('clean-webpack-plugin')
const webpack = require('webpack');
module.exports = {
//....
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpakcPlugin({
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin()
]
}
这样,如果只改了css
样式,页面不会从新加载,只会重新加载css
文件。
如果对于js
文件,也想通过热模块更新,在webpack.config.js
里面的配置跟上面一样,但是我们在代码里面需要进行判断,加一些代码逻辑,用来判断是否开启热模块更新,然后进行处理我们的逻辑:需要进行监控我们需要修改的文件
// 如果支持热模块加载
if(module.hot) {
// 监控number文件,如果发生改变,就会执行里面的代码。
// 第一个参数为依赖的文件的名字,
module.hot.accept('./number', ()=> {
document.body.removeChild(document.getElementById('number'))
number();
})
}
查看官方文档
guides
里面的Hot Module Replacement
中的内容深入了解,然后查看api
查看Hot Module Replacement
配置选项,然后进行查看concepts
里面的Hot Module Replacement
内容,进行更深入的了解。
# 8. 使用 Babel 处理 ES6 语法
# 8.1 使用 Babel 按需打包js文件
首先进行安装,输入命令npm install --save-dev babel-loader @babel/core
,然后在webpack.config.js
增加一个规则,如下:代码意思就是除了node_modules
里面的js
内容不检测,其他的js
文件文件都通过babel-loader
进行加载转换。
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
}
其实babel-loader
只是Babel
与webpack
之间的一个通讯工具,babel-loader
并不会将es6
语法翻译为es5
语法,而babel/preset-env
才是真正转换的工具。首选进行安装,输入命令:npm install @babel/preset-env --save-dev
,然后进行配置在webpack.config.js
:
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
]
}
也可以新建一个.babelrc
的文件,然后里面增加配置,启用一些插件。
{
"presets": ["@babel/preset-env"]
}
这种转换只是一部分,低版本的有些函数还是不存在的,比如promise
等函数,我们还需要借助@babel/polyfill
,进行对低版本浏览器对es6
中这些函数的支持,输入命令进行安装:npm install --save @babel/polyfill
,然后全局引入,放在业务代码的最顶部,
import "@babel/polyfill";
这样全局引入,会将所有es6
的新增的函数打包到js
文件中,这样会使打包的文件体积增大。我们需要的是在该js
文件中使用的es6
函数的实现,在webpack.config.js
进行配置;
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [["@babel/preset-env",{
useBuiltIns: 'usage'
}]]
}
}
]
}
就会实现按需加载,如果js
中有用到es6
新增函数,会将对应的实现代码打包到js
中,而不是将全部es6
新增的函数实现方式打包进去。
# 8.2 打包转换中的其他参数(设置打包支持浏览器的版本、编写类库文件babel转换配置)
我们在打包的选项中,还可以进行配置其他选项,比如可以进行配置打包支持浏览器的版本,babel
可以根据打包后支持的浏览器,去进行判断需要打包哪一些es6
语法的实现函数,代码如下:这句代码是设置了打包后的为高于谷歌67版本的浏览器。
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [["@babel/preset-env",{
targets: {
chrome: "67",
},
useBuiltIns: 'usage'
}]]
}
}
]
}
我们如果写业务代码需要使用babel
转换的时候,使用上面的配置就可以了,我们如果编写一个类库或者一个UI组件,需要进行不同的配置,配置步骤如下:
安装plugin-transform-runtime
,输入命令:npm install --save-dev @babel/plugin-transform-runtime
,然后进行安装babel/runtime
,输入命令:npm install --save @babel/runtime
,然后进行安装babel/runtime-corejs2
,输入命令:npm install --save @babel/runtime-corejs2
,然后在webpack.config.js
进行配置;这样配置的好处是,我们在使用babel
转换的时候,借助polyfill
在全局进行注入,这样会污染全局,使用下面的配置,他会以闭包的形式,进行注入支持es6
语法的函数。不存在全局污染。
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
"plugins": [["@babel/plugin-transform-runtime",{
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}]]
}
}]
},
如果我们配置的options
会很多的时候,可以在项目里面新建一个.babelrc
的文件,将options
里面的内容写在这里。然后去掉webpack.config.js
中我们配置的babel-loader
中options
里面的内容。
# 9. Webpack 实现对React框架代码的打包
写react
首选需要安装他,输入命令npm install react react-dom --save
,然后在我们的js
文件写react
代码,如下:
import "@babel/polyfill";
import React, {Component} from 'react'
import ReactDom from 'react-dom'
class App extends Component {
return () {
return <div>Hello World</div>
}
}
ReactDom.render(<App />, document.getElementById('root'))
安装babel/preset-react
,进行转换;输入命令npm install --save-dev @babel/preset-react
,然后在.babelrc
文件中增加配置:
{
presets: [
[
"@babel/preset-env", {
targets: {
chrome: "67",
},
useBuiltIns: 'usage'
}
],
"@babel/preset-react"
]
}
这里的插件执行是从下往上执行的,就是先进行打包react
代码,然后进行es6
代码的转换。
# 10. Webpack 打包的一些坑
是遇到很多坑,比如使用了html-webpack-plugin
插件,然后我们允许webpack-dev-server
命令的时候,我们配置了打包入口的html
模板,但是启动服务之后,没有页面显示,查看打包信息出现Entrypoint undefined = index.html
,webpack.config.js
配置如下:
// 引入node核心模块path
const path = require('path')
// 将我们写的html文件,进行打包;
const HtmlWebpakcPlugin = require('html-webpack-plugin')
// 清除上次打包生成的js文件
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',
// sub: './src/index.js'
},
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
proxy: {
'/api': 'http://localhost:3000'
},
hot: true,
hotOnly: true
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 204800
}
}
}, {
test: /\.vue$/,
use: {
loader: 'vue-loader'
}
}, {
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.(eot|ttf|svg|woff)$/,
use: {
loader: 'file-loader'
}
}, {
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}]
},
plugins: [
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpakcPlugin({
template: './src/index.html'
}),
],
optimization: {
usedExports: true
},
// 打包出的文件配置
output: {
// 文件引入的cnd地址
// publicPath: 'http://cdn.com.cn',
publicPath: './',
// 文件名
filename: '[name].js',
// 打包后的文件放在哪个文件夹,是一个绝对路径
// __dirname就是webpack.config.js所在的当前目录的路径,当前模块的目录名,改成bundle就是说,打包后的文件放在dist文件夹中
path: path.resolve(__dirname, 'dist')
}
}
后来经过测试,将输出配置成如下:
// 打包出的文件配置
output: {
path: path.resolve(__dirname, 'dist')
}
这样就可以了,不要配置,打包输出的文件名以及地址就可以,虽然是解决了允许服务器没有显示页面的问题,但是这个解决办法还不是最佳。
评 论:
进阶知识 →