Webpack 是当下最热门的前端资源模块化管理和打包工具。它能够将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还能够将按需加载的模块进行代码分隔,等到实际须要的时候再异步加载。经过loader的转换,任何形式的资源均可以视做模块,好比 CommonJs 模块、AMD 模块、ES6 模块、CSS、图片、JSON、Coffeescript、LESS 等。javascript
前端是基于多语言、多层次的编码和组织工做,其次前端产品的交付是基于浏览器,这些资源是经过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,而且保证他们在浏览器端快速、优雅的加载和更新,就须要一个模块化系统。css
Webpack 具备四个核心的概念,想要入门 Webpack 就得先好好了解这四个核心概念。它们分别是Entry(入口)、Output(输出)、loader 和 Plugins(插件)。接下来详细介绍这四个核心概念。html
Entry 是 Webpack 的入口起点指示,它指示 webpack 应该从哪一个模块开始着手,来做为其构建内部依赖图的开始。能够在配置文件(webpack.config.js)中配置 entry 属性来指定一个或多个入口点,默认为./src( webpack 4开始引入默认值)。 具体配置方法:前端
entry: string | Array<string>
复制代码
前者一个单独的 string 是配置单独的入口文件,配置为后者(一个数组)时,是多文件入口。java
//webpack.config.js
module.exports = {
entry: {
app: './app.js',
vendors: './vendors.js'
}
};
复制代码
以上配置表示从 app 和 vendors 属性开始打包构建依赖树,这样作的好处在于分离本身开发的业务逻辑代码和第三方库的源码,由于第三方库安装后,源码基本就再也不变化,这样分开打包有利于提高打包速度,减小了打包文件的个数。node
Output 属性告诉webpack在哪里输出它所建立的 bundles,也可指定 bundles 的名称,默认位置为 ./dist。整个应用结构都会被编译到指定的输出文件夹中去,最基本的属性包括 filename(文件名)和 path(输出路径)。react
值得注意的是,便是你配置了多个入口文件,你也只能有一个输出点。jquery
具体配置方法:webpack
output: {
filename: 'bundle.js',
path: '/home/proj/public/dist'
}
复制代码
值得注意的是,output.filename 必须是绝对路径,若是是一个相对路径,打包时 webpack 会抛出异常。css3
多个入口时,使用下面的语法输出多个 bundle :
// webpack.config.js
module.exports = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}
复制代码
loader 能够理解为webpack的编译器,它使得webpack能够处理一些非 JavaScript 文件,好比 png、csv、xml、css、json 等各类类型的文件,使用合适的 loader 可让 JavaScript 的 import 导入非 JavaScript 模块。JavaScript 只认为 JavaScript 文件是模块,而 webpack 的设计思想即万物皆模块,为了使得 webpack 可以认识其余“模块”,因此须要 loader 这个“编译器”。
webpack 中配置 loader 有两个目标:
(1)test 属性:标志有哪些后缀的文件应该被处理,是一个正则表达式。
(2)use 属性:指定 test 类型的文件应该使用哪一个 loader 进行预处理。
好比webpack.config.js:
module.exports = {
entry: '...',
output: '...',
module: {
rules: [
{
test: /\.css$/,
use: 'css-loader'
}
]
}
};
复制代码
该配置文件指示了全部的 css 文件在 import 时都应该通过 css-loader 处理,通过 css-loader 处理后,能够在 JavaScript 模块中直接使用 import 语句导入 css 模块。可是使用 css-loader 的前提是先使用 npm 安装 css-loader。
此处须要注意的是定义 loaders 规则时,不是定义在对象的 rules 属性上,而是定义在 module 属性的 rules 属性中。
配置多个 loader:
有时候,导入一个模块可能要先使用多个 loader 进行预处理,这时就要对指定类型的文件配置多个 loader 进行预处理,配置多个 loader,把 use 属性赋值为数组便可,webpack 会按照数组中 loader 的前后顺序,使用对应的 loader 依次对模块文件进行预处理。
{
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
}
]
}
]
}
}
复制代码
loader 用于转换非 JavaScript 类型的文件,而插件能够用于执行范围更广的任务,包括打包、优化、压缩、搭建服务器等等,功能十分强大。要是用一个插件,通常是先使用npm包管理器进行安装,而后在配置文件中引入,最后将其实例化后传递给 plugins 数组属性。
插件是 webpack 的支柱功能,目前主要是解决 loader 没法实现的其余许多复杂功能,经过 plugins 属性使用插件:
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
}
复制代码
模式( Mode )能够经过配置对象的 mode 属性进行配置,主要值为 production 或者 development。两种模式的区别在于一个是为生产环境编译打包,一个是为了开发环境编译打包。生产环境模式下,webpack 会自动对代码进行压缩等优化,省去了配置的麻烦。
学习完以上基本概念以后,基本也就入门 webpack 了,由于 webpack 的强大就是创建在这些基本概念之上,利用 webpack 多样的 loaders 和 plugins,能够实现强大的打包功能。
命名空间是经过为项目或库建立一个全局对象,而后将全部功能添加到该全局变量中。经过减小程序中全局变量的数量,实现单全局变量,从而在具备大量函数、对象和其余变量的状况下不会形成全局污染,同时也避免了命名冲突等问题。
然而,在不一样的文件中给一个命名空间添加属性的时候,首先要保证这个命名空间是已经存在的,同时不对已有的命名空间形成任何破坏。能够经过非破坏性的命名空间函数实现:
var KUI = KUI || {};
KUI.utils = KUI.utils || {};
KUI.utils.namespace = function(ns){
var parts = ns.split("."),
object = KUI,
i, len;
if(parts[0] === "KUI"){
parts = parts.slice(1);
}
for(i = 0, len = parts.length; i < len; i+=1){
if(!object[parts[i]]){
object[parts[i]] = {};
}
object = object[parts[i]];
}
return object;
};
复制代码
用法:
KUI.utils.namespace("KUI.common");
KUI.utils.namespace("KUI.common.testing");
KUI.utils.namespace("KUI.modules.function.plugins");
KUI.utils.namespace("format");
复制代码
看一下通过上述后 KUI 都有什么:
{
"utils": {},
"common": {
"testing": {}
},
"modules": {
"function": {
"plugins": {}
}
},
"format": {}
}
复制代码
命名空间模式的缺点
1.须要输入更长的字符,而且须要更长的解析时间; 2.对单全局变量的依赖性,即任何代码均可以修改该全局实例,其余代码将得到修改后的实例。
CommonJS 是 nodejs 也就是服务器端普遍使用的模块化机制。 该规范的主要内容是,模块必须经过 module.exports 导出对外的变量或接口,经过 require() 来导入其余模块的输出到当前模块做用域中。
根据这个规范,每一个文件就是一个模块,有本身的做用域,文件中的变量、函数、类等都是对其余文件不可见的。
若是想在多个文件分享变量,必须定义为 global 对象的属性。
在每一个模块内部,module 变量表明当前模块。它的 exports 属性是对外的接口,将模块的接口暴露出去。其余文件加载该模块,实际上就是读取 module.exports 变量。
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
复制代码
require 方法用于加载模块,后缀名默认为.js
var app = require('./app.js');
复制代码
模块加载的顺序,按照其在代码中出现的顺序
根据参数的不一样格式,require 命令去不一样路径寻找模块文件。
通常都会有一个主文件(入口文件),在 index.html 中加载这个入口文件,而后在这个入口文件中加载其余文件。
能够经过在 package.json 中配置 main 字段来指定入口文件。
第一次加载某个模块时,Node 会缓存该模块。之后再加载该模块,就直接从缓存取出该模块的 module.exports 属性。
CommonJS 模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
因为 CommonJS 是同步加载模块,这对于服务器端不是一个问题,由于全部的模块都放在本地硬盘。等待模块时间就是硬盘读取文件时间很小。可是,对于浏览器而言,它须要从服务器加载模块,涉及到网速,代理等缘由,一旦等待时间过长,浏览器处于”假死”状态。
AMD 是 "Asynchronous Module Definition" 的缩写,即 “异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。
这里异步指的是不堵塞浏览器其余任务( dom 构建,css 渲染等),而加载内部是同步的(加载完模块后当即执行回调)。
requirejs 即为遵循AMD规范的模块化工具。
RequireJS 的基本思想是,经过 define 方法,将代码定义为模块;经过 require 方法,实现代码的模块加载。
RequireJS 主要解决两个问题:
RequireJS 定义了一个函数 define,它是全局变量,用来定义模块:
define(id?, dependencies?, factory);
复制代码
参数说明:
id:指定义中模块的名字,可选;若是没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。若是提供了该参数,模块名必须是“顶级”的和绝对的(不容许相对名字)。
依赖 dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。 依赖参数是可选的,若是忽略此参数,它应该默认为["require", "exports", "module"]。然而,若是工厂方法的长度属性小于 3 ,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。
工厂方法 factory,模块初始化要执行的函数或对象。若是为函数,它应该只被执行一次。若是是对象,此对象应该为模块的输出值。
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
复制代码
AMD 也采用 require 命令加载模块,可是不一样于 CommonJS ,它要求两个参数:
require(['math'], function(math) {
math.add(2, 3);
})
复制代码
第一个参数是一个数组,里面的成员是要加载的模块,第二个参数是加载完成后的回调函数。
require 方法自己也是一个对象,它带有一个 config 方法,用来配置 require.js 运行参数。
require.config({
paths: {
"backbone": "vendor/backbone",
"underscore": "vendor/underscore"
},
shim: {
"backbone": {
deps: [ "underscore" ],
exports: "Backbone"
},
"underscore": {
exports: "_"
}
}
});
复制代码
paths:paths 参数指定各个模块的位置。这个位置能够是同一个服务器上的相对位置,也能够是外部网址。能够为每一个模块定义多个位置,若是第一个位置加载失败,则加载第二个位置。上面就是指定了 jquery 的位置,那么就能够直接在文件中
require(['jquery'],function($){})
复制代码
shim:有些库不是 AMD 兼容的,这时就须要指定 shim 属性的值。shim 能够理解成“垫片”,用来帮助require.js 加载非 AMD 规范的库。
CMD 即Common Module Definition 通用模块定义,CMD 规范是国内发展出来的,就像 AMD 有个requireJS,CMD 有个浏览器的实现 SeaJS,SeaJS 要解决的问题和 requireJS 同样,只不过在模块定义方式和模块加载(能够说运行、解析)时机上有所不一样。
在 CMD 规范中,一个模块就是一个文件。代码的书写格式以下:
define(function(require, exports, module) {
// 模块代码
});
复制代码
require 是能够把其余模块导入进来的一个参数; 而 exports 是能够把模块内的一些属性和方法导出的; module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖能够就近书写
b.doSomething()
// ...
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})
复制代码
ES6 正式提出了内置的模块化语法,咱们在浏览器端无需额外引入 requirejs 来进行模块化。ES6 在语言标准的层面上,实现了模块功能,并且实现得至关简单,彻底能够取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块不是对象,而是经过export命令显式指定输出的代码,再经过 import 命令输入。
ES6 中的模块有如下特色:
使用 export 关键字将任意变量、函数或者类公开给其余模块。
//导出变量
export var color = "red";
export let name = "cz";
export const age = 25;
//导出函数
export function add(num1,num2){
return num1+num2;
}
//导出类
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
function multiply(num1, num2) {
return num1 * num2;
}
//导出对象,即导出引用
export {multiply}
复制代码
重命名想导出的变量、函数或类的名称
function sum(num1, num2) {
return num1 + num2;
}
export {sum as add}
复制代码
这里将本地的 sum 函数重命名为 add 导出,所以在使用此模块的时候必须使用 add 这个名称。
模块的默认值是使用 default 关键字所指定的单个变量、函数或类,而你在每一个模块中只能设置一个默认导出。
export default function(num1, num2) {
return num1 + num2;
}
复制代码
此模块将一个函数做为默认值进行了导出, default 关键字标明了这是一个默认导出。此函数并不须要有名称,由于它就表明这个模块自身。对比最前面使用 export 导出的函数,并非匿名函数而是必须有一个名称用于加载模块的时候使用,可是默认导出则无需一个名字,由于模块名就表明了这个导出值。
也可使用重命名语法来导出默认值。
function sum(num1, num2) {
return num1 + num2;
}
export { sum as default };
复制代码
在模块中使用 import 关键字来导入其余模块。 import 语句有两个部分,一是须要导入的标识符,二是需导入的标识符的来源模块。此处是导入语句的基本形式:
import { identifier1,identifier2 } from "./example.js"
复制代码
当从模块导入了一个绑定时,你不能在当前文件中再定义另外一个同名变量(包括导入另外一个同名绑定),也不能在对应的 import 语句以前使用此标识符,更不能修改它的值。
//导入单个绑定
import {sum} from './example.js'
//导入多个绑定
import {sum,multiply} from './example.js'
//彻底导入一个模块
import * as example from './example.js'
example.sum(1,2);
example.multiply(2,3);
//重命名导入
import { sum as add} from './example.js'
//导入默认值
import sum from "./example.js";
复制代码
然而要记住,不管你对同一个模块使用了多少次 import 语句,该模块都只会被执行一次。
在导出模块的代码执行以后,已被实例化的模块就被保留在内存中,并随时都能被其余 import 所引用.
import { sum } from "./example.js";
import { multiply } from "./example.js";
import { magicNumber } from "./example.js";
复制代码
尽管此处的模块使用了三个 import 语句,但 example.js 只会被执行一次。若同一个应用中的其余模块打算从 example.js 导入绑定,则那些模块都会使用这段代码中所用的同一个模块实例。
export 与 import 都有一个重要的限制,那就是它们必须被用在其余语句或表达式的外部,而不能使用在if等代码块内部。缘由之一是模块语法须要让 JS 能静态判断须要导出什么,正由于此,你只能在模块的顶级做用域使用 export 与 import。
webpack 对各类模块化的支持
// app.js
// es module
import sum from './sum'
// commonjs
var minus = require('./minux')
//amd
require(['muti'], function () {
console.log(muti(2, 3))
})
console.log(sum(2, 3))
console.log(minus(3, 2))
复制代码
// sum.js
export default function () {
return a + b
}
复制代码
// minus.js
module.exports = function (a, b) {
a - b
}
复制代码
// muti.js
define(function() {
'use strict';
return function (a, b) {
return a * b;
}
});
复制代码
如今你写的 JS 代码,在上线以前,都是须要进行压缩的,在没有 webpack 和 gulp 这些工具前,你可能须要找一个压缩软件或者在线进行压缩,在Webpack中能够很轻松的实现JS代码的压缩,它是经过插件的方式实现的,这里咱们就先来引入一个 uglifyjs-webpack-plugin ( JS 压缩插件,简称 uglify)。
注意:虽然 uglifyjs 是插件,可是webpack版本里默认已经集成,不须要再次安装。
引入:
咱们须要在 webpack.config.js 中引入 uglifyjs-webpack-glugin 插件
const uglify = require('uglifyjs-webpack-plugin');
复制代码
引入后在 plugins 配置里new一个 uglify 对象就能够了,代码以下。
plugins:[
new uglify()
],
复制代码
这时候在终端中使用 webpack 进行打包,你会发现 JS 代码已经被压缩了。
在前端开发中都开始使用ES6的语法了,虽说 webpack3 增长了一些 ES6 的转换支持,可是实际效果不是很好。因此我在开发中仍是喜欢添加 Babel-loader 的,我也查看了一些别人的 webpack 配置也都增长了 babel-loader,因此这节课咱们学习一下如何增长 Babel 支持。
Babel 是什么? Babel 实际上是一个编译 JavaScript 的平台,它的强大之处表如今能够经过便宜帮你达到如下目的:
Babel 实际上是几个模块化的包,其核心功能位于称为 babel-core 的 npm 包中,webpack 能够把其不一样的包整合在一块儿使用,对于每个你须要的功能或拓展,你都须要安装单独的包(用得最多的是解析 ES6 的 babel-preset-es2015 包和解析 JSX 的 babel-preset-react 包)。
安装依赖包
npm install --save-dev babel-loader babel-core babel-preset-env
复制代码
在 webpack 中配置 Babel 的方法以下:
{
test:/\.(jsx|js)$/,
use:{
loader:'babel-loader',
options:{
presets:[
"es2015","react"
]
}
},
exclude:/node_modules/
}
复制代码
虽然 Babel 能够直接在 webpack.config.js 中进行配置,可是考虑到 babel 具备很是多的配置选项,若是卸载 webapck.config.js 中会很是的雍长不可阅读,因此咱们常常把配置卸载 .babelrc 文件里。
在项目根目录新建 .babelrc 文件,并把配置写到文件里。
. babelrc
{
"presets":["react","es2015"]
}
复制代码
.webpack.config.js 里的 loader 配置
{
test:/\.(jsx|js)$/,
use:{
loader:'babel-loader',
},
exclude:/node_modules/
}
复制代码
babel-preset-env 代替 babel-preset-ES2015 , babel 官方推出了 babel-preset-env ,并建议在使用的时候选择 env 代替以前的 ES20** 。env 为咱们提供了更智能的编译选择。
npm install --save-dev babel-preset-env
复制代码
而后修改 .babelrc 里的配置文件。其实只要把以前的 es2015 换成 env 就能够了。
{
"presets":["react","env"]
}
复制代码
CommonsChunkPlugin 插件,是一个可选的用于创建一个独立文件 (又称做 chunk ) 的功能,这个文件包括多个入口 chunk 的公共模块。
经过将公共模块拆出来,最终合成的文件可以在最开始的时候加载一次,便存到缓存中供后续使用。这个带来速度上的提高,由于浏览器会迅速将公共的代码从缓存中取出来,而不是每次访问一个新页面时,再去加载一个更大的文件。
生成一个额外的 chunk 包含入口 chunk 的公共模块。
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
// ( 公共chunk(commnons chunk) 的名称)
filename: "commons.js",
// ( 公共chunk 的文件名)
// minChunks: 3,
// (模块必须被3个 入口 chunk 共享)
// chunks: ["pageA", "pageB"],
// (只使用这些 入口chunk)
})
复制代码
你必须在 入口 chunk 以前加载生成的这个公共 chunk:
<script src="commons.js" charset="utf-8"></script>
<script src="entry.bundle.js" charset="utf-8"></script>
复制代码
将你的代码拆分红公共代码和应用代码。
entry: {
vendor: ["jquery", "other-lib"],
app: "./entry"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
// filename: "vendor.js"
// (给 chunk 一个不一样的名字)
minChunks: Infinity,
// (随着 entry chunk 愈来愈多,
// 这个配置保证没其它的模块会打包进 vendor chunk)
})
]
复制代码
使用代码拆分功能,一个 chunk 的多个子 chunk 会有公共的依赖。为了防止重复,能够将这些公共模块移入父 chunk。这会减小整体的大小,但会对首次加载时间产生不良影响。若是预期到用户须要下载许多兄弟 chunks(例如,入口 trunk 的子 chunk),那这对改善加载时间将很是有用。
new webpack.optimize.CommonsChunkPlugin({
// names: ["app", "subPageA"]
// (选择 chunks,或者忽略该项设置以选择所有 chunks)
children: true,
// (选择全部被选 chunks 的子 chunks)
// minChunks: 3,
// (在提取以前须要至少三个子 chunk 共享这个模块)
})
复制代码
与上面的相似,可是并不是将公共模块移动到父 chunk(增长初始加载时间),而是使用新的异步加载的额外公共chunk。当下载额外的 chunk 时,它将自动并行下载。
new webpack.optimize.CommonsChunkPlugin({
name: "app",
// or
names: ["app", "subPageA"]
// the name or list of names must match the name or names
// of the entry points that create the async chunks
children: true,
// (选择全部被选 chunks 的子 chunks)
async: true,
// (建立一个异步 公共chunk)
minChunks: 3,
// (在提取以前须要至少三个子 chunk 共享这个模块)
})
复制代码
webpack 能够帮助咱们将代码分红不一样的逻辑块,在须要的时候加载这些代码。
require.ensure() 是一种使用 CommonJS 的形式来异步加载模块的策略。在代码中经过 require.ensure([]) 引用模块,其使用方法以下:
require.ensure(dependencies: String[], callback: function(require), chunkName: String); 复制代码
第一个参数指定依赖的模块,第二个参数是一个函数,在这个函数里面你可使用 require 来加载其余的模块,webpack 会收集 ensure 中的依赖,将其打包在一个单独的文件中,在后续用到的时候使用 jsonp 异步地加载进去。
//进行代码分割
require.ensure(['lodash'],function(){
var _ = require('lodash');//上边的require.ensure只会引入进来,可是并不会执行,再次require才会执行。
},'vendor')
复制代码
或者
if(page=='subPageA'){
require.ensure(['./subPageA'],function(){
var subPageA=require('subPageA');
},'subPageA')
}else if(page=='subPageB'){
require.ensure(['./subPageB'],function(){
var subPageA=require('subPageB');
},subPageB)
}
复制代码
或者
require.ensure(['./subPageA','./subPageB'],function(){
var subPageA=require('subPageA');
var subPageB=require('subPageB');
},common)
//common表示这个模块的名字
复制代码
可是仅仅这样配置并不能把公共 js 抽离出来,在多页面应用中能够经过 new webpack.optimize.CommonsChunkPlugin 这个 plugin 来实现,可是对于单页面来讲,就须要借助 require.include 了
require.include('./moduleA')
if(page=='subPageA'){
require.ensure(['./subPageA'],function(){
var subPageA=require('subPageA');
},'subPageA')
}else if(page=='subPageB'){
require.ensure(['./subPageB'],function(){
var subPageA=require('subPageB');
},subPageB)
}
复制代码
这样就会把公共模块 moduleA 给抽离出来。
import 与 require.ensure 最大的区别就是,他在引入的时候会直接执行,而不须要在此 require 了
import('./subPageA').then(function(){
})
复制代码
可是这样打包出来的是没有 chunkname 的,怎么添加 chunkname 呢?须要 webpack3+ 的魔法注释
import(/*webpackChunkName:'subPageA'*/'./subPageA').then(function(){
})
复制代码
首先,在 src 目录下创建 css 文件夹,和 index.css 文件,并编写以下代码:
body{
background: burlywood;
color:white;
font-size:30px;
}
复制代码
创建好后,须要引入到入口文件中,才能够打包。在 entery.js 的首行加入代码:
import css from './css/index.css';
复制代码
CSS 和引入作好后,咱们就须要使用 loader 来解析 CSS 文件了,这里咱们须要两个解析用的 loader,分别是 style-loader 和 css-loader。
它是用来处理 css 文件中的 url() 等。 用 npm install 进行项目安装:
npm install --save-dev style-loader
复制代码
它是用来将 css 插入到页面的 style 标签。 用 npm install 进行项目安装:
npm install --save-dev css-loader
复制代码
修改 webpack.config.js 中 module 属性中的配置代码以下:
webpack.config.js
module:{
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
]
},
复制代码
目前,打包后的文件中,css 是打包在 js 代码里面的,这样不便于之后的维护,因此须要把 CSS 从 js 中分离出来,咱们须要使用插件 Extract Text Plugin。
安装:
npm install --save-dev extract-text-webpack-plugin
复制代码
在 webpack.config.js 中引入
const ExtractTextPlugin = require('extract-text-webpack-plugin');
复制代码
在 Plugins中配置:
new ExtractTextPlugin('css/index.css');
//css/index.css是分离后的路径位置
复制代码
修改 Loader 配置:
module:{
rules:[
{
test:/\.css$/,
use:ExtractTextPlugin.extract({
fallback:"style-loader",
use:"css-loader"
})
}
]
}
复制代码
Less 做为目前很火的 CSS 预处理语言,它扩展了 CSS 语言,增长了变量、Mixin 、函数等特性,使 CSS 更易维护和扩展;
安装:
npm install --save-dev less less-loader
复制代码
在 webpack.config.js 中配置 Loader:
module:{
rules:[
{
test:/\.less$/,
use:ExtractTextPlugin.extract({
fallback:"style-loader",
use:[{
loader:"css-loader"
},{
loader:"less-loader"
}]
})
}
]
}
复制代码
Sass 的打包和分离和 less 的相似,首先下载安装 Sass 所支持的服务与 loader。 安装:
npm install --save-dev node-sass sass-loader
复制代码
在 webpack.config.js 中配置 Loader:
module:{
rules:[
{
test:/\.less$/,
use:ExtractTextPlugin.extract({
fallback:"style-loader",
use:[{
loader:"css-loader"
},{
loader:"sass-loader"
}]
})
}
]
}
复制代码
CSS3 是目前做为一个前端必需要掌握的技能,可是因为如今好多浏览器仍是不兼容 CSS3,因此前端须要多写很丑很难看的前缀代码;之前都是边查 Can I Use ,边添加,这样很麻烦,如今配置一个插件 postcss就能够搞定;
PostCSS 是一个 CSS 的处理平台,它能够帮助你的 CSS 实现更多的功能,可是今天咱们就经过其中的一个加前缀的功能,初步了解一下 PostCSS。
安装:
npm install --save-dev postcss-loader autoprefixer
复制代码
在根目录下,创建一个 postcss.config.js 文件:
module.exports = {
plugins:[
require('autoprefixer')
]
}
复制代码
这就是对 postCSS 一个简单的配置,引入了 autoprefixer 插件。让 postCSS 拥有添加前缀的能力,它会根据 can i use 来增长相应的css3属性前缀。
在 webpack.config.js 中配置 Loader:
{
test: /\.css$/,
use: extractTextPlugin.extract({
fallback: 'style-loader',
use: [
{ loader: 'css-loader',
options: { importLoaders: 1 }
},
'postcss-loader'
]
})
}
复制代码
Tree-shaking 字面意思就是摇晃树, 其实就是去除那些引用的但却没有使用的代码。 Tree-shaking 概念最先由 Rollup.js 提出,后来在 webpack2 中被引入进来,可是这个这一特性可以被支持得益于 ES6 modules 的静态特性。ES6的模块声明相比于传统 CommonJS 的同步 require 有着本质区别。这种 modules 设计保证了依赖关系是提早肯定的,使得静态分析成为了可能,与运行时无关。 而且 webpack 中并无直接对 tree-shaking 的配置,须要借助 uglifyjs-webpack-plugin。
webpack 中 tree-shaking主要分为两个方面:
将文件标记为无反作用( side-effect-free ) 在一个纯粹的 ESM 模块世界中,识别出哪些文件有反作用很简单。然而,咱们的项目没法达到这种纯度,因此,此时有必要向 webpack 的 compiler 提供提示哪些代码是“纯粹部分”。
这种方式是经过 package.json 的 "sideEffects" 属性来实现的。
{
"name": "your-project",
"sideEffects": false
}
复制代码
如同上面提到的,若是全部代码都不包含反作用,咱们就能够简单地将该属性标记为 false,来告知 webpack,它能够安全地删除未用到的 export 导出。
「反作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export 。举例说明,例如 polyfill ,它影响全局做用域,而且一般不提供 export 。
若是你的代码确实有一些反作用,那么能够改成提供一个数组:
{
"name": "your-project",
"sideEffects": [
"./src/some-side-effectful-file.js"
]
}
复制代码
压缩输出 经过如上方式,咱们已经能够经过 import 和 export 语法,找出那些须要删除的“未使用代码(dead code)”,然而,咱们不仅是要找出,还须要在 bundle 中删除它们。为此,咱们将使用 -p(production) 这个 webpack 编译标记,来启用 uglifyjs 压缩插件。
注意,--optimize-minimize 标记也会在 webpack 内部调用 UglifyJsPlugin。 从 webpack 4 开始,也能够经过 "mode" 配置选项轻松切换到压缩输出,只需设置为 "production"。
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode: "production"
};
复制代码
为了学会使用 tree shaking,你必须……
像 Bootstrap 这样的框架每每会带有不少 CSS。在项目中一般咱们只使用它的一小部分。就算咱们本身写CSS,随着项目的进展,CSS 也会愈来愈多,有时候需求更改,带来了 DOM 结构的更改,这时候咱们可能无暇关注 CSS 样式,形成不少 CSS 的冗余。
PurifyCSS 使用 PurifyCSS 能够大大减小 CSS 冗余,好比咱们常用的 BootStrap (140KB)就能够减小到只有 35KB 大小。这在实际开发当中是很是有用的。
安装 PurifyCSS-webpack 从名字你就能够看出这是一个插件,而不是 loader。因此这个须要安装还须要引入。 PurifyCSS-webpack 要以来于 purify-css 这个包,因此这两个都须要安装。
npm i –save-dev purifycss-webpack purify-css
复制代码
引入 glob 由于咱们须要同步检查html模板,因此咱们须要引入 node 的 glob 对象使用。在 webpack.config.js 文件头部引入 glob。
const glob = require('glob');
复制代码
引入 purifycss-webpack 一样在 webpack.config.js 文件头部引入 purifycss-webpack
const PurifyCSSPlugin = require("purifycss-webpack");
复制代码
配置 plugins 引入完成后咱们须要在 webpack.config.js 里配置 plugins 。代码以下,重点看标黄部分。
plugins:[
//new uglify()
new htmlPlugin({
minify:{
removeAttrubuteQuotes:true
},
hash:true,
template:'./src/index.html'
}),
new extractTextPlugin("css/index.css"),
new PurifyCSSPlugin({
// Give paths to parse for rules. These should be absolute!
paths: glob.sync(path.join(__dirname, 'src/*.html')),
})
]
复制代码
这里配置了一个 paths ,主要是需找 html 模板,purifycss 根据这个配置会遍历你的文件,查找哪些css 被使用了。
配置好上边的代码,咱们能够故意在 css 文件里写一些用不到的属性,而后用 webpack 打包,你会发现没用的 CSS 已经自动给你删除掉了。在工做中记得必定要配置这个 plugins ,由于这决定你代码的质量,很是有用。
在 index.html 文件中增长一个放置 div 的标签
<div id="tupian"></div>
复制代码
编写 css 文件,把图片做为背景显示。
#tupian{
background-image: url(../images/manhua.png);
width:466px;
height:453px;
}
复制代码
安装 file-loader 和 url-loader
npm install --save-dev file-loader url-loader
复制代码
file-loader :解决引用路径的问题,拿 background 样式用 url 引入背景图来讲,咱们都知道, webpack 最终会将各个模块打包成一个文件,所以咱们样式中的 url 路径是相对入口 html 页面的,而不是相对于原始 css 文件所在的路径的。这就会致使图片引入失败。这个问题是用 file-loader 解决的,file-loader 能够解析项目中的 url 引入(不只限于 css),根据咱们的配置,将图片拷贝到相应的路径,再根据咱们的配置,修改打包后文件引用路径,使之指向正确的文件。 url-loader:若是图片较多,会发不少 http 请求,会下降页面性能。这个问题能够经过 url-loader 解决。url-loader 会将引入的图片编码,生成 dataURl 。至关于把图片数据翻译成一串字符。再把这串字符打包到文件中,最终只须要引入这个文件就能访问图片了。固然,若是图片较大,编码会消耗性能。所以url-loader 提供了一个 limit 参数,小于 limit 字节的文件会被转为 DataURl ,大于 limit 的还会使用 file-loader 进行 copy。
配置 url-loader 咱们安装好后,就可使用这个 loader 了,记得在 loader 使用时不须要用 require 引入,在plugins 才须要使用 require 引入。
webpack.config.js文件
//模块:例如解读 CSS,图片如何转换,压缩
module:{
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
},{
test:/\.(png|jpg|gif)/ ,
use:[{
loader:'url-loader',
options:{
limit:500000
}
}]
}
]
},
复制代码
有的小伙伴会发现咱们并无在 webpack.config.js 中使用 file-loader ,可是依然打包成功了。咱们须要了解 file-loader 和 url-loader 的关系。url-loader 和 file-loader 是什么关系呢?简答地说,url-loader 封装了 file-loader 。 url-loader 不依赖于 file-loader ,即便用 url-loader 时,只须要安装 url-loader 便可,不须要安装 file-loader ,由于 url-loader内置了 file-loader 。经过上面的介绍,咱们能够看到,url-loader 工做分两种状况:
1.文件大小小于 limit 参数, url-loader 将会把文件转为 DataURL( Base64格式 );
2.文件大小大于 limit , url-loader 会调用 file-loader 进行处理,参数也会直接传给 file-loader。
也就是说,其实咱们只安装一个 url-loader 就能够了。可是为了之后的操做方便,咱们这里就顺便安装上 file-loader。
前边两节课程,打包后的图片并无放到images文件夹下,要放到 images 文件夹下,其实只须要配置咱们的 url-loader 选项就能够了。
module:{
rules: [
{
test: /\.css$/,
use: extractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},{
test:/\.(png|jpg|gif)/ ,
use:[{
loader:'url-loader',
options:{
limit:5000,
outputPath:'images/',
}
}]
}
]
},
复制代码
在处理 css 时咱们已经学会如何使用 extract-text-webpack-plugin 插件提取 css,利用 extract-text-webpack-plugin 插件很轻松的就把 CSS 文件分离了出来,可是 CSS 路径并不正确,不少小伙伴就在这里搞个几天仍是没有头绪,网上也给出了不少的解决方案,我觉的最好的解决方案是使用publicPath 解决,我也一直在用。
publicPath:是在 webpack.config.js 文件的 output 选项中,主要做用就是处理静态文件路径的。
在处理前,咱们在 webpack.config.js 上方声明一个对象,叫 website。
var website ={
publicPath:"http://192.168.1.108:1717/"
}
复制代码
注意,这里的 IP 和端口,是你本机的 ip 或者是你 devServer 配置的 IP 和端口。 而后在 output 选项中引用这个对象的 publicPath 属性。
//出口文件的配置项
output:{
//输出的路径,用了Node语法
path:path.resolve(__dirname,'dist'),
//输出的文件名称
filename:'[name].js',
publicPath:website.publicPath
},
复制代码
配置完成后,你再使用 webpack 命令进行打包,你会发现原来的相对路径改成了绝对路径,这样来说速度更快。
{
test:/\.(png|woff|woff2|svg|ttf|eot)$/,
use:{
loader:'url-loader',
options: {
limit: 100000, //这里要足够大这样全部的字体图标都会打包到css中
}
}
复制代码
上文中的 limit 必定要保证大于最大字体文件的大小,由于这个参数是告诉 url-loader,若是文件小于这个参数,那么就以 Data Url 的方式直接构建到文件中。使用这种方式最方便,不用打包后路径的问题,可是缺点就是构建出来的文件特别大,若是线上不要使用这种方式打包。
{
test: /\.(woff|woff2|svg|ttf|eot)$/,
use:[
{
loader:'file-loader',
options:{name:'fonts/[name].[hash:8].[ext]'}}
//项目设置打包到dist下的fonts文件夹下
]
}
复制代码
打包中会遇到的问题就是路径不对,能够经过配置 publicPath 解决。
在实际工做中,咱们的项目都会配置一个 Json 的文件或者说 API 文件,做为项目的配置文件。有时候你也会从后台读取到一个 json 的文件,这节课就学习如何在 webpack 环境中使用 Json。若是你会 webpack1 或者 webpack2 版本中,你是须要加载一个 json-loader 的 loader 进来的,可是在webpack3.x 版本中,你再也不须要另外引入了。
读出 Json 内容 第一步:如今咱们的 index.html 模板中加入一个层,并给层一个 Id,为了是在 javascript 代码中能够方便引用。
<div id="json"></div>
复制代码
第二步:到 src 文件夹下,找到入口文件,我这里是 entry.js 文件。修改里边的代码,以下:
var json =require('../config.json');
document.getElementById("json").innerHTML= json.name;
复制代码
这两行代码很是简单,第一行是引入咱们的 json 文件,第二行驶写入到到 DOM 中。
html-webpack-plugin 能够根据你设置的模板,在每次运行后生成对应的模板文件,同时所依赖的 CSS/JS 也都会被引入,若是 CSS/JS 中含有 hash 值,则 html-webpack-plugin 生成的模板文件也会引入正确版本的 CSS/JS 文件。
安装
npm install html-webpack-plugin --save-dev
复制代码
引入
在webpack.config.js中引入:
const HtmlWebpackPlugin = require('html-webpack-plugin');
复制代码
配置
module.exports = {
entry: './app/index.js',
output: {
...
},
module: {
...
},
plugins: [
new HtmlWebpackPlugin({
title: "This is the result",
filename: "./index.html",
template: "./app/index.html",
inject: "body",
favicon: "",
minify: {
caseSensitive: false,
collapseBooleanAttributes: true,
collapseWhitespace: true
},
hash: true,
cache: true,
chunks: ""
})
]
};
复制代码
而后看一下这些参数的意义:
html-withimg-loader html-withimg-loader 就是咱们今天的重点了,这个插件并非很火,也是我我的喜欢的一个小loader 。解决的问题就是在hmtl文件中引入 标签的问题。
安装:
npm install html-withimg-loader --save
复制代码
配置 loader webpack.config.js
{
test: /\.(htm|html)$/i,
use:[ 'html-withimg-loader']
}
复制代码
而后在终端中能够进行打包了。你会发现 images 被很好的打包了。而且路径也彻底正确。
在使用 webpack-cli 进行打包时,经过命令 webpack --watch便可开启 watch 模式,进入 watch 模式以后,一旦依赖树中的某一个模块发生了变化,webpack 就会从新进行编译。
在 webpack 中打包生成的文件会覆盖以前的文件,不过生成文件的时候文件名加了 hash 以后会每次都生成不同的文件,这就会很麻烦,不但会生成不少冗余的文件,还很难搞清楚究竟是哪一个文件,这就须要引入该插件
npm install –save-dev clean-webpack-plugin
复制代码
//webpack.config.js
//引入clean-webpack-plugin
const CleanWebpackPlugin = require('clean-webpack-plugin');
//plugin 插入你想删除的路径,注意在生成出来文件以前,他会删除 public 的文件夹,而不是根据生成的文件来删除对应的文件。
new CleanWebpackPlugin(['public']);
复制代码
webpack-dev-server 简介:
安装webpack-dev-server
npm install webpack-dev-server --save-dev
复制代码
在 webpack.config.js 中添加配置
var webpack=require('webpack');
module.exports = {
……
devServer: {
historyApiFallback: true,
inline: true,//注意:不写hot: true,不然浏览器没法自动更新;也不要写 colors:true,progress:true等,webpack2.x已不支持这些
},
plugins:[
……
new webpack.HotModuleReplacementPlugin()
]
……
};
复制代码
在 package.json 里配置运行的命令
"scripts":
{
  "start": "webpack-dev-server --inline"
},
复制代码
若是你有单独的后端开发服务器 API,而且但愿在同域名下发送 API 请求 ,那么代理某些 URL 会颇有用。 webpack-dev-server 使用了很是强大的 http-proxy-middleware 包。
配置以下:
proxy: {
'/apis': {
target: '', //要代理到的地址
secure: false, //若地址为https,须要设置为false
onProxyReq: function(proxyReq, req, res) { //提早设置一些代理的头部,如token信息等
},
//...其余配置请自行查阅文档http-proxy-middleware文档
}
}
复制代码
DevServer 还支持一 种叫作模块热替换( Hot Module Replacement )的技术可在不刷新整个网页的状况下 作到超 灵敏实时预览。原理是在一个源码发生变化时,只需从新编译发生变化的模块,再用新输 出 的模块替换掉浏览器中对应的老模块 。
模块热替换技术的优点以下:
总的来讲,模块热替换技术在很大程度上提高了开发效率和体验 。
DevServer 默认不会开启模块热替换模式,要开启该模式,则只 需在启动时带上参数 --hot ,完整的命令是 webpack-dev-server --hot。
除了经过在启动时带上 --hot 参数,还能够经过接入 Plugin 实现,相关代码以下 :
canst HotModuleReplacementPlugin = require (’ webpack/lib/HotModuleReplacementPlugin ’);
module.exports = {
entry:{
//为每一个入口都注入代理客户端
main: [’ webpack-dev-server/client?http://localhost:8080 /’, ’webpack/hot/dev-server ’,’. / src/main.j s ’],
},
plugIns : [
//该插件的做用就是实现模块热替换,实际上若启动时带上 、 --hot 、参数,就会注入该插件,生 成 .hot-update.json 文件。
new HotModuleReplacementPlugin() ,
],
devServer : {
//告诉 DevServer 要开启 模块热替换模式
hot: true ,
},
};
复制代码
借助于 style-loader 的帮助,CSS 的模块热替换其实是至关简单的。当更新 CSS 依赖模块时,此 loader 在后台使用 module.hot.accept 来修补(patch) <style>
标签。
但当修改 js 文件时,咱们会发现模块热替换没有生效,而是整个页面被刷新了,为了让使用者在使用模块热替换功能时能灵活地控制老模块被替换时的逻辑,webpack 容许在源码中定义一些代码去作相应的处理。
// 只有当开启了模块热替换时 module.hot 才存在
if (module.hot) {
module.hot.accept(['.IAppComponent'],()=>{
//在新的 AppComponent 加载成功后从新执行组建渲染逻辑 render(<AppComponentl>, window.document.getElementByid ('app'));
}) ;
}
复制代码
其中的 module.hot 是当开启模块热替换后注入全局的 API,用于控制模块热替换的逻辑 。 当子模块发生更新时,更新事件会一层层地向上传递,也就是从 AppComponent.js 文件传递到 main.js 文件,直到有某层的文件接收了当前变化的模块,即 main.js 文 件中定义的 module.hot.accept(['.IAppComponent'], callback),这时就会调用 callback 函数去执行自定义逻辑。 若是事件一直往上抛,到最外层都没有文件接收它,则会直接刷新网页。
做为一个程序员天天的大部分工做就是调试本身写的程序,那咱们使用了webpack后,因此代码都打包到了一块儿,给调试带来了麻烦,可是webpack已经为咱们充分考虑好了这点,它支持生产 Source Maps 来方便咱们的调试。 在使用 webpack 时只要经过简单的 devtool 配置,webapck 就会自动给咱们生产 source maps 文件,map 文件是一种对应编译文件和源文件的方法,让咱们调试起来更简单。
在配置 devtool 时,webpack 给咱们提供了四种选项:
我的意见是,若是大型项目可使用 source-map,若是是中小型项目使用 eval-source-map 就彻底能够应对,须要强调说明的是,source map 只适用于开发阶段,上线前记得修改这些调试设置。
简单的配置:
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
}
}
复制代码
首先,要使 webpack 支持 eslint,就要要安装 eslint-loader ,命令以下:
npm install --save-dev eslint-loader
复制代码
在 webpack.config.js 中添加以下代码:
{
test: /\.js$/,
loader: 'eslint-loader',
enforce: "pre",
include: [path.resolve(__dirname, 'src')], // 指定检查的目录
options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
formatter: require('eslint-friendly-formatter') // 指定错误报告的格式规范
}
}
复制代码
注:formatter 默认是 stylish ,若是想用第三方的能够安装该插件,如上方的示例中的 eslint-friendly-formatter 。
其次,要想 webpack 具备 eslint 的能力,就要安装 eslint,命令以下:
npm install --save-dev eslint
复制代码
最后,项目想要使用那些 eslin 规则,能够建立一个配置项文件 '.eslintrc.js',代码以下:
module.exports = {
root: true,
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
},
rules: {
"indent": ["error", 2],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"no-console": "error",
"arrow-parens": 0
}
}
复制代码
这样,一个简单的 webpack 引入 eslint 已经完成了。
webpack 确实是一个功能强大的模块打包工具,丰富的 loader 和 plugin 使得其功能多而强。学习 webpack 使得咱们能够自定义本身的开发环境,无需依赖 create-react-app 和 Vue-Cli 这类脚手架,也能够针对不一样的需求对代码进行不一样方案的处理。