在查找webpack的资料的时候,发现大部分的资料对webpack的定义都是这样
webpack是当下最热门的前端资源模块化管理和打包工具(就是一个打包器),可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源,还可以将需要加载的模块进行代码分离,等到实际需要的时候再进行异步加载。通过loader的转换,任何资源都可以视作模块,如COMMONJS模块 ,AMD模块,JS CSS ,JSON 等 ,它会根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
一、模块与模块化
也就是说,webpack就是对模块进行处理的一个打包工具,那么问题又来了。什么是模块?
根据我的理解就是,一个模块里面它就是一个文件,这个文件里面它含有很多函数,从而实现特定的功能可以供里外使用。就像是我们平时直接用script引入一个插件一样,这些插件它就是一个模块。
那么什么是模块化呢?
就是将代码区分成一个一个的模块,等到需要什么样的功能的时候就加载什么模块。
所以既然这样模块化开发,那么它就需要遵循一定的规范,否则按照每个人都有一套标准,那么共同开发起来就有了困难。
那么这样就有了一个模块化规范
规范一:CommonJS
规范二:AMD
规范三:CMD
下面来说说这三个规范的特点
1.CommonJS规范(参考阮一峰老师的教程http://javascript.ruanyifeng.com/nodejs/module.html)
CommonJS规范是服务器端模块的规范,Node.JS就采用了这个规范。根据CommonJS规范,一个单独的文件就是一个模块,每一个模块就是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性。
使用module.exports来输出模块变量。使用require方法来同步加载模块(同步以为阻塞)
// module add.js
module.exports = function add (a, b) { return a + b; }
// main.js
var {add} = require('./math');
console.log('1 + 2 = ' + add(1,2);
CommonJS规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(module.exports)是对外的接口,当你加载某个模块的时候,其实是加载该模块的module.exports属性
var x = 5;
var addX = function (value) {
return value + x;};
module.exports.x = x;
module.exports.addX = addX;
像上面的代码,本来在模块内部定义的x和addX是只能在模块内部使用的,但是使用module.exports进行输出了变量,那么其他模块引入这个模块以后,就可以直接使用x和addX了。
require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。
require命令用于加载文件,后缀名默认为.js。
var foo = require('foo');
// 等同于
var foo = require('foo.js');
根据参数的不同格式,require命令去不同路径寻找模块文件。
(1)如果参数字符串以“/”开头,则表示加载的是一个位于绝对路径的模块文件。比如,require('/home/marco/foo.js')将加载/home/marco/foo.js。
(2)如果参数字符串以“./”开头,则表示加载的是一个位于相对路径(跟当前执行脚本的位置相比)的模块文件。比如,require('./circle')将加载当前脚本同一目录的circle.js。
(3)如果参数字符串不以“./“或”/“开头,则表示加载的是一个默认提供的核心模块(位于Node的系统安装目录中),或者一个位于各级node_modules目录的已安装模块(全局安装或局部安装)。
举例来说,脚本/home/user/projects/foo.js执行了require('bar.js')命令,Node会依次搜索以下文件。
-
/usr/local/lib/node/bar.js
-
/home/user/projects/node_modules/bar.js
-
/home/user/node_modules/bar.js
-
/home/node_modules/bar.js
-
/node_modules/bar.js
在阮一峰老师的教程里,指明CommonJS的特点如下
-
所有代码都运行在模块作用域,不会污染全局作用域。
-
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
-
模块加载的顺序,按照其在代码中出现的顺序。
(4)如果参数字符串不以“./“或”/“开头,而且是一个路径,比如require('example-module/path/to/file'),则将先找到example-module的位置,然后再以它为参数,找到后续路径。
(5)如果指定的模块文件没有发现,Node会尝试为文件名添加.js、.json、.node后,再去搜索。.js件会以文本格式的JavaScript脚本文件解析,.json文件会以JSON格式的文本文件解析,.node文件会以编译后的二进制文件解析。
(6)如果想得到require命令加载的确切文件名,使用require.resolve()方法。
目录的加载规则
通常,我们会把相关的文件会放在一个目录里面,便于组织。这时,最好为该目录设置一个入口文件,让require方法可以通过这个入口文件,加载整个目录。
在目录中放置一个package.json文件,并且将入口文件写入main字段。
// package.json
{ "name" : "some-library",
"main" : "./lib/some-library.js" }
require发现参数字符串指向一个目录以后,会自动查看该目录的package.json文件,然后加载main字段指定的入口文件。如果package.json文件没有main字段,或者根本就没有package.json文件,则会加载该目录下的index.js文件或index.node文件。
模块的缓存
第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。
require('./example.js');
require('./example.js').message = "hello";
require('./example.js').message// "hello"
上面代码中,连续三次使用require命令,加载同一个模块。第二次加载的时候,为输出的对象添加了一个message属性。但是第三次加载的时候,这个message属性依然存在,这就证明require命令并没有重新加载模块文件,而是输出了缓存。
如果想要多次执行某个模块,可以让该模块输出一个函数,然后每次require这个模块的时候,重新执行一下输出的函数。
所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写。
// 删除指定模块的缓存delete require.cache[moduleName];
// 删除所有模块的缓存Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];})
注意,缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require命令还是会重新加载该模块。
CommonJS的加载原理
require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
{
id: '...',
exports: { ... },
loaded: true,
...}
上面代码中,该对象的id属性是模块名,exports属性是模块输出的各个接口,loaded属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有很多属性,这里都省略了
以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。
// 1. 检查 Module._cache,是否缓存之中有指定模块
// 2. 如果缓存之中没有,就创建一个新的Module实例
// 3. 将它保存到缓存
// 4. 使用 module.load() 加载指定的模块文件,
// 读取文件内容之后,使用 module.compile() 执行文件代码
// 5. 如果加载/解析过程报错,就从缓存删除该模块
// 6. 返回该模块的 module.exports
CommonJS的重要特性是加载时执行,即脚本代码在require的时候就会全部执行,一旦某个模块被循环加载,就只输出已经执行的部分,还没执行的部分不会输出
2.AMD规范
AMD就是Asynchronous Module Definition,异步模块定义,也就是说模块是被异步加载的,模块加载不影响后面语句的运行。所以AMD规范普遍用于浏览器端模块开发的规范。
所有依赖某些模块的语句都会被放置到回调函数中。
AMD规范模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。
define(id?, dependencies?, factory);
id:指定模块的名字,可选,如果没有提供该参数,模块的名字应该默认为模块加载器请求脚本的名字
dependencies:当前模块依赖的,已经被模块定义的模块标识的数组字面量,可选,,它应该默认为["require", "exports", "module"]。
factory:模块的功能,函数或者对象
define(function (){
var add = function (x,y){
return x+y;
};
return {
add: add
};
});
像上面的代码,它不依赖别的模块,就可以直接在define里面进行定义,这个模块定义了一个函数,进行两个数的相加。那么在主文件或者别的模块就可以像下面这样加载
require(['math'], function (math){
alert(math.add(1,1));
});
如果需要依赖别的模块,那么就像下面这样定义
define(['./a', './b'], function(a, b) {
// 依赖必须一开始就写好
a.add1()
...
b.add2()
...
})
AMD也是使用require这个命令来加载模块,但不同于CommonJS只要一个参数,它要求两个参数
require([module], callback);
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数,加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
require(['math'], function (math) {
math.add(2, 3);
});
目前,实现AMD的库有RequireJS 、curl 、Dojo 、Nodules 等。
RequireJs
(1)RequireJS要求每个模块放在一个单独的文件中,对模块是预执行
(2)使用RequireJS的步骤:
-
下载最新版本的RequireJS,注意的是要将它和其他的js文件放在一个目录下面:https://requirejs.org/docs/download.html
-
引入到文件中:<script src="js/require.js" defer async="true" ></script>(异步加载js文件,因为ie不支持async属性,只支持defer属性,所以都写上)
-
指定主文件:<script src="js/require.js" data-main="js/main"></script>,data-main的作用是指定网页程序的主模块,上面知道你的是js/main.js,这个文件会第一个被require.js加载。
-
书写主模块:
如果我们的代码不依赖任何其他的代码,就可以直接写入JavaScript代码
如果依赖其他的模块,就是用require来引入
// main.js
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// some code here
});
上面代码是依赖ABC三个模块。
但是如果这些模块跟require.js不再同一个目录的话,就需要配置模块的加载路径了。
使用require.config()方法,我们可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径
require.config({
paths: {
"jquery": "lib/jquery.min",
"underscore": "lib/underscore.min",
"backbone": "lib/backbone.min"
}
});
如果模块所在的目录一致,就可以直接指定基目录
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min",
"underscore": "underscore.min",
"backbone": "backbone.min"
}
});
也可以直接指定网址
require.config({
paths: {
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min"
}
});
但是因为每个模块都是单独的js文件,所以如果加载的模块过多,就会发出过多的http请求,从而影响性能,影响网页加载速度。所以,require.js又提供了一个优化工具(天使啊你)
可以使用跟这个工具将多个模块合并在一个文件中,减少HTTP请求数。
二、webpack
(一)webpack是什么?为什么要用webpack?
webpack就是前段资源模块化管理和打包工具,将松散的模块按照依赖和规则打包成符合胜男环境部署的前端资源,还可以将需要加载的模块进行代码分离,等到实际需要的时候再进行异步加载。通过loader的转换,任何资源都可以视作为模块,它会根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
就是,在模块化开发的时候,一个模块一个文件,这就需要进行多次的HTTP请求,影响网页的加载速度,那么有人就想着将所有的模块压缩成一个文件,但是这样,有的时候初次加载的时候我还没有用到这个模块,这样就会导致流浪的浪费,初始化过程缓慢。
所以,编译的时候对所有的代码进行静态分析,分析出各个模块的类型以及依赖关系,然后将它们提交给适配的加载器处理。(按需加载)
所以这就是webpack的功能了。
webpack的工作方式是:把项目当做一个整体,通过一个给定的主文件(如mian.js),webpack将从这个文件开始找到你的项目中所有的依赖文件,使用loaders处理它们,最后打包成一个或者多个浏览器可以识别的JavaScript文件。
webpack中的一些核心概念
1.入口(entry):入口七点只是webpack应该使用哪个模块,来构建其内部依赖图的开始,webpack会找出有哪些模块和library是入口起点的,默认值是./src/index.js,可以在webpack.config.js中来配置entry的属性,来制定一个或多个入口起点。
module.exports = {
entry: './path/to/my/entry/file.js'};
如果给entry传入一个数组的时候,将创建多个主入口。上面的写法是单个入口的简写语法,放只有一个入口起点的时候,这个配置比较好。
此外还有对象语法来配置entry
module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}};
上面的写法就是从不同的入口来创建以来图,这些依赖图是彼此独立的。
多页面应用程序就推荐这种写法。
2.输出(output)(只能有一个输出配置):出口告诉webpack在哪里输入它所创建的输出文件(分析解析的结果,多个模块所结合起来的文件),以及如何命名这些文件,主输出文件默认为./dist/main.js,其他生成文件的默认输出目录是./dist。也可以在webpack.config.js中进行配置
module.exports = {
output: {
filename: 'bundle.js',
path: '/home/proj/public/assets'
}};
此配置将一个单独的 bundle.js 文件输出到 /home/proj/public/assets 目录中
如果设置了多个入口的起点,那么就要使用占位符来确保每个文件具有唯一的名称
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}};
// 写入到硬盘:./dist/app.js, ./dist/search.js
3.loader:loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!
示例
首先安装相对应的loader
npm install --save-dev css-loader
npm install --save-dev ts-loader
然后指示 webpack 对每个 .css 使用 css-loader,以及对所有 .ts 文件使用 ts-loader:
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
{ test: /\.ts$/, use: 'ts-loader' }
]
}};
当webpack编译器碰到「在 require()/import 语句中被解析为 '.css和.ts 的路径」时,在你对它打包之前,先使用 对应的loader转换一下。”
重要的是要记得,在 webpack 配置中定义 loader 时,要定义在 module.rules 中,而不是 rules。为了使你受益于此,如果没有按照正确方式去做,webpack 会给出警告。
4.插件(plugins):如果需要使用一些插件,首先要require它,然后把它添加到plugins的数组中,
5.模式(mode)参数设置为开发模式( development)和生产模式( production ),
module.exports = {
mode: 'production'};
6.基本配置
var path = require('path');
module.exports = {
mode: 'development',
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),//将字符串拼凑成一个可用的文件名
filename: 'foo.bundle.js'
}};
(二)如何配置webpack
1、安装,首先先安装node.js,在node中安装npm
(1)安装node,去node官网下载并安装https://nodejs.org/en/
安装后,打开cmd,输入node -v就可以看到相应的版本。
(2)、安装webpack
npm install webpack -g(全局安装webpack)
然后可以webpack -v查看版本
(3)npm初始化:npm init
可用发现,文件中多了一个package.json微博就爱你,这是一个标准的npm说明文件,里面蕴含了丰富的信息,包括当前项目的依赖模块,自定义的脚本等等。终端会问你一系列的问题,直接回车默认就好。
(4)本地安装webpack:npm install webpack webpack-cli --save-dev
可以看到此时多了package-lock.json这个文件和node_modules这个目录。node_modules里面的就是各种依赖包,以后我们用npm安装的包也会被放在这里。
(5)新建一个目录src(源代码所在的目录),一个index.html文件还有一个在src目录下面的index.js文件,一个dist目录(构建过程产生的代码最小化和优化后的“输出”目录,最终将在浏览器中加载)
(7)写一个demo
-
安装lodash依赖:npm install --save-dev lodash
-
各个文件的代码如下
//index.html和dist/index.html
<!doctype html>
<html>
<head>
<title>hello</title>
<meta charset="utf-8">
</head>
<body>
<script src="main.js"></script>
</body>
</html>
//src/index.js
import _ from 'lodash';
function component() {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
document.body.appendChild(component());
然后打开命令行
输入npx webpack
执行npx webpack,会将我们的脚本作为入口起点,然后输出为main.js。
然后此时可以看到dist目录下面多了一个main.js文件,此时运行dist下面的index.html可以看到
如果使用es6以及更高的es版本的时候,要确保webpack的loader系统中使用了babel转译器。
(三)怎么使用webpack
1.如果需要使用配置文件,在项目目录下面添加一个webpack.config.js文件,在里面添加下面配置
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}};
编译的时候,指定配置文件进行构建(如果不指名具体的哦诶之文件,那么webpack将会寻找是否具有这个文件,如果有将会默认使用它。)
npx webpack --config webpack.config.js
如果不想要每次编译的时候都执行npx webpack,那么在package.json中的script中添加代码build:webpack:
{
"name": "mywebpack",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"lodash": "^4.17.11",
"webpack": "^4.29.3",
"webpack-cli": "^3.2.3"
},
"dependencies": {}
}
那么现在执行npm run build就可以了(等同于npx webpack)
可以使用向npm run build 命令中和你的参数之间添加两个中横线,将自定义的参数传递给webpack
如npm run build -- --colors
2.通过webpack来管理资源
(1)管理css
首先需要安装一下style加载器以及css加载器
npm install --save-dev style-loader css-loader
然后再配置文件中指明加载器,下面加粗代码中,webpack根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的loader。下满情况中,以.css结尾的全部文件,豆浆被提供给style-loader,和css-loader
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'mybundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test:/\.css$/,
use: [
"style-loader",
"css-loader"
]
}
]
}
};
这样,当在一个模块当中依赖css文件,import ".css"文件的时候,含有css字符的style标签就会被插入到html文件中的head中
下面再src目录下面添加一个css文件,并在index.js中引入这个文件
//mywebpack.css
.mywebpack{
color: red;
font-size: 22px;
font-weight: bold;
}
//index.js
import _ from 'lodash';
import "./mywebpack.css";
function component() {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add("mywebpack");
return element;
}
document.body.appendChild(component());
然后执行构建命令npm run build
打开浏览器可以看到css样式起效了
(2)加载图片
步骤与上面一样,都是先npm安装对应的加载器,然后修改对应的配置文件,然后再模块对应的index.js文件中引入
npm install --save-dev file-loader
然后再module下的rule属性添加下面的规则
{
test:/\.(png|svg|jpg|jpeg|gif)$/,
use: [
"file-loader"
]
}
当你 import MyImage from './my-image.png',该图像将被处理并添加到 output 目录,_并且_ MyImage 变量将包含该图像在处理后的最终 url。
loader 会识别这是一个本地文件,并将 './my-image.png' 路径,替换为输出目录中图像的最终路径。html-loader 以相同的方式处理 <img src="./my-image.png" />。
然后再index.js中添加上面的代码
import _ from 'lodash';
import "./mywebpack.css";
import myimg from "./mywebpack.gif";
function component() {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add("mywebpack");
let gif = new Image();
gif.src = myimg;
element.appendChild(gif);
return element;
}
document.body.appendChild(component());
执行npm run build
打开浏览器
可以看到图片已经被加载进来。那么如果我们不想在index.js中加载进来,直接在页面中加载进来怎么办呢?
下面我们来实现以下,在index.html中引入图片
然后我发现,因为图片还是在src目录下面而不是在dist下面,路径还是出现了问题。
然后看了官网文档,发现htnl-loader可以实现。那么放到后面讲html-loader的时候再实现。
(3)加载字体
上面加载图片的时候已经添加的file-loader,这个加载器可以接收并加载任何文件,并且将其输出到构建目录中,也就是说可以将它们任用与任何类型的文件,包括字体。那么现在我们只需要更新配置文件,在文件中引入即可。
在rule中添加
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
把文件字体放到src目录下面,然后如果需要在css文件中引入就引入使用就好了。
然后执行npm run build
(4)还有其他的加载器
csv、xml
npm install --save-dev csv-loader xml-loader
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader'
]
},
{
test: /\.xml$/,
use: [
'xml-loader'
]
}
import Data from './data.xml';
3.上一小节说的都是如何在项目中导入,现在来说一下怎么输出。
如果我们需要配置多个多个入口,并且让webpack在dist中生成不同的文件,例如,我们的src目录下面有两个文件,分别是index.js和print.js文件。
我们在webpack.config.js中配置我们两个不同的入口
entry: {
app: './src/index.js',
print: './src/print.js'
},
然后我们希望能够根据不同的入口和文件生成不同的输出文件,还需要在output中设置:
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
上面的代码就是希望我们能够根据不同的文件名生成不同的以budle.js后缀的文件
然后执行npm run build
可以看到,此时已经生成了app.bundle.js文件以及print.bundle.js两个文件了。
所以此时我们在文件中可以使用这两个文件了。
但是,如果我们在webpack.config.js中修改了我们的配置使得webpack生成的文件名发生了变化,但是在文档中引入的还是原来的文件名,这时候如果手工进行修改会相当的麻烦,那么此时就用到了一个插件:HtmlWebpackPlugin。
下面来说一下这个插件的使用方法:
(1)安装文件
npm install --save-dev html-webpack-plugin
(2)修改webpack.config.js中的配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
plugins: [
new HtmlWebpackPlugin({
title: 'Output Management'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
此时,即使dist文件夹中已经拥有了index.html文件,但是插件它还是会默认生成index.html文件,并且会把原来的文件覆盖掉。
执行npm run build
然后打开index.html文件,此时可以发现,所有的bundle文件已经自动的添加到了html中。
此外,还有一个插件用来在每次构建前清理一下dist文件夹,这样只会生成本次构建需要用到的文件。
首先安装clean-webpack-plugin
npm install clean-webpack-plugin
然后再配置文件webpack.config.js中进行如下的配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Output Management'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
然后执行npm run build就可以了。
4.如果希望代码更新了之后,编译器能实时的更新编译,而不用每次都手动输入npm run build
那么,就用到了一个webpack-dev-server这个插件,webpack-dev-server 为你提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。
首先安装这个插件:npm install --save-dev webpack-dev-server
然后在webpack.config.js进行修改,改配置文件,告诉开发服务器(dev server),在哪里查找文件:
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:"development",
entry: {
app: './src/index.js',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Output Management'
}),
new CleanWebpackPlugin(['dist']),
new webpack.HotModuleReplacementPlugin(),
],
output: {
filename: '[name].my.js',
path: path.resolve(__dirname, 'dist')
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
hot: true
},
};
以上配置告知 webpack-dev-server,在 localhost:8080 下建立服务,将 dist 目录下的文件,作为可访问文件。
让我们添加一个 script 脚本,可以直接运行开发服务器(dev server)
//package.json
{
"name": "mywebpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"watch": "webpack --watch",
"start": "webpack-dev-server --open"
},
"author": "",
"license": "ISC",
"devDependencies": {
"lodash": "^4.17.11",
"webpack": "^4.29.4",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.1.14"
}
}
现在,我们可以在命令行中运行 npm start,就会看到浏览器自动加载页面。如果现在修改和保存任意源文件,web 服务器就会自动重新加载编译后的代码。
5.如果有的时候,有些插件的有些功能我们并没有用到,但是整个包又很大,我们就想要精简一下代码。那么在webpack中,有一个tree shaking的概念,意思就是移除那些没有用到的代码。
安装插件
npm install --save-dev uglifyjs-webpack-plugin
6、构建生产环境
首先安装webpack-merge,将代码进行分离(就是将配置文件分成开发版以及生产环境版本)
npm install --save-dev webpack-merge
然后将webpack.config.js分离成webpack.common.js、webpack.dev.js、webpack.prod.js三个文件。
其中webpack.common.js的代码如下
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');//安装在重新构建前清理dist文件夹的插件
const HtmlWebpackPlugin = require('html-webpack-plugin');//安装自动向index文件添加js文件的插件
module.exports = {
entry: {
app: './src/index.js'
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Production'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
webpack.dev.js文件如下
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
}
});
webpack.prod.js
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
plugins: [
new UglifyJSPlugin()
]
});
然后在package.json中配置脚本,将start配置为开发环境,build配置为生产环境。
然后在调试代码中,开发环境使用inline-source-map。生产环境使用source-map。
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
plugins: [
new UglifyJSPlugin({
sourceMap: true
})
]
});
此时可以发现,执行npm run build 编译文件后,只要你用npm start进行开发就可以了
5.编译es6
安装babel以及banbel-presets
npm install --save-dev babel-core
npm install --save-dev babel-loader@7
npm install --save-dev babel-preset-env
然后再创建一个.babelrc文件,并将内容写成如下:
{
"presets":[
"env"
]
}
然后再webpack.config.js中指明loader规则
rules:[{
test:/\.js$/,
use:{
loader:"babel-loader",
options{
presets:['babel-preset-env']
}
}]
还有两个插件:(按需安装)
开发应用时使用的插件babel Polyfill
npm install -save babel-polyfill
开发框架时使用的拆那babel runtime transform
npm install --save-dev babel-plugin-transform-runtime
npm install -save babel-runtime
6.代码分离
代码分离是webpack中的一个特性,能够将代码分离到不同的bundle中,然后可以按需加载或者并行加载这些文件,代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
代码分离的方法主要有以下三种
-
入口起点,通过在配置文件汇总手动配置entry来进行分离代码
-
CommonsChunkPlugin 插件(将公共的模块放到一个新的chunk或者公共chunk中)
-
动态导入:通过模块的内联函数调用来分离代码
7.懒加载
懒加载就是按需加载,是一种很好的优化网页的方式,就是先把代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或者即将引用另一些新的代码块,这样加快了应用的初始加载速度,减轻了它的总体体积。
在vue中的懒加载有以下三个方式
-
组件,异步组件
-
路由
-
vuex
全局注册组件:
Vue.component("AsyncCmp", () => import("./AsyncCmp"));
局部注册组件:
new Vue({
// ...
components: {
AsyncCmp: () => import("./AsyncCmp")
}});
路由:
// Instead of: import Login from './login'const Login = () => import("./login");
new VueRouter({
routes: [{ path: "/login", component: Login }]});
vuex
const store = new Vuex.Store()
...
// Assume there is a "login" module we wanna loadimport('./store/login').then(loginModule => {
store.registerModule('login', loginModule)})
8.缓存
当我们部署新版本的时候如果不更改资源的文件名,浏览器可能会认为它没有被更新,就是使用它的缓存版本,由于缓存的存在,当需要获取新的代码的时候,就会显得几首。通过配置以确保webpack编译生成的文件能够被客户端缓存,而在文件内容发生变化之后能够请求到新的文件。