写在前面
第一次接触webpack,是在一个react项目参与中,刚开始使用的时候,甚至不知道是做什么用的,只看到webpack.config.js文件中很多配置,但是都不太清楚是何用处,本地开发调试模式和build到生产环境都分不清,甚至曾经在服务器上用开发模式运行过一段时间,菜的抠脚啊!最早只知道jQuery操控dom,js、css引入到html,浏览器就渲染出页面了。完全不了解webpack这种构建工具,还可以通过express搭建个native-server,来实时调试代码修改,当然了这个功能一般都是webpack自动完成的。我们可以在本机端口,访问到我们的页面,代码的修改保存可以实时刷新重新渲染。
一、概念
webpack 是一个现代 JavaScript 应用程序的模块打包器(module bundler)。所谓的模块就是在平时的前端开发中,用到一些静态资源,如JavaScript、CSS、图片等文件,webpack就将这些静态资源文件称之为模块。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成少量的 bundle - 通常只有一个,由浏览器加载。
它是高度可配置的,但是,在开始前你需要先理解四个核心概念:入口(entry)、输出(output)、loader、插件(plugins)。
1.入口(Entry)
webpack 创建应用程序所有依赖的关系图(dependency graph)。图的起点被称之为入口起点(entry point)。入口起点告诉 webpack 从哪里开始,并根据依赖关系图确定需要打包的内容。可以将应用程序的入口起点认为是根上下文(contextual root) 或 app 第一个启动文件。
在 webpack 中,我们使用 webpack 配置对象(webpack configuration object) 中的entry
属性来定义入口。
接下来我们看一个最简单的例子:webpack.config.js
module.exports = { entry: './path/to/my/entry/file.js'};
根据不同应用程序的需要,声明entry属性有多种方式。
2.出口(Output)
将所有的资源(assets)归拢在一起后,还需要告诉 webpack 在哪里打包应用程序。webpack 的output属性描述了如何处理归拢在一起的代码(bundled code)。
webpack.config.js
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
}}
在上面的例子中,我们通过output.filename和output.path
属性,来告诉 webpack bundle 的名称,以及我们想要生成(emit)到哪里。
你可能看到项目生成(emitted 或 emit)贯穿我们整个文档和插件 API。它是“生产(produced)”或“排放(discharged)”的特殊术语。
3.Loader
webpack 的目标是,让 webpack 聚焦于项目中的所有资源(asset),而浏览器不需要关注考虑这些(明确的说,这并不意味着所有资源(asset)都必须打包在一起)。webpack 把每个文件(.css, .html, .scss, .jpg, etc.) 都作为模块处理。然而 webpack 自身只理解 JavaScript。
webpack loader 在文件被添加到依赖图中时,其转换为模块。**
在更高层面,在 webpack 的配置中 loader 有两个目标。
识别出(identify)应该被对应的 loader 进行转换(transform)的那些文件。(test
属性)
转换这些文件,从而使其能够被添加到依赖图中(并且最终添加到 bundle 中)(use属性)
webpack.config.js
const path = require('path');
const config = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [ {
test: /\.txt$/,
use: 'raw-loader'
} ] }
};
module.exports = config;
以上配置中,对一个单独的 module 对象定义了rules属性,里面包含两个必须属性:test和use。这告诉 webpack 编译器(compiler) 如下信息:
“嘿,webpack 编译器,当你碰到「在require()/import语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先使用 raw-loader转换一下。”
重要的是要记得,在 webpack 配置中定义 loader 时,要定义在module.rules中,而不是rules。然而,在定义错误时 webpack 会给出严重的警告。为了使你受益于此,如果没有按照正确方式去做,webpack 会“给出严重的警告”
4.插件(Plugins)
然而由于 loader 仅在每个文件的基础上执行转换,而插件(plugins)
更常用于(但不限于)在打包模块的 “compilation” 和 “chunk” 生命周期执行操作和自定义功能(查看更多)。webpack 的插件系统极其强大和可定制化。
想要使用一个插件,你只需要require()它,然后把它添加到plugins
数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用new
来创建它的一个实例。
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');//installed via npmconst
webpack = require('webpack'); //to access built-in plugins
const path = require('path');
const config = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [ {
test: /\.txt$/,
use: 'raw-loader'
} ] },
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]};
module.exports = config;
二、Webpack的核心原理
Webpack的两个最核心的原理分别是:
- 一切皆模块正如js文件可以是一个“模块(module)”一样,其他的(如css、image或html)文件也可视作模 块。因此,你可以require('myJSfile.js')亦可以require('myCSSfile.css')。这意味着我们可以将事物(业务)分割成更小的易于管理的片段,从而达到重复利用等的目的。
- 按需加载传统的模块打包工具(module bundlers)最终将所有的模块编译生成一个庞大的bundle.js文件。但是在真实的app里边,“bundle.js”文件可能有10M到15M之大可能会导致应用一直处于加载中状态。因此Webpack使用许多特性来分割代码然后生成多个“bundle”文件,而且异步加载部分代码以实现按需加载。
三、几处说明
1.开发模式和生产模式
在package.json文件加入如下的scripts项:
"scripts": {
// 运行npm run build 来编译生成生产模式下的bundles
"build": "webpack --config webpack.config.prod.js",
// 运行npm run dev来生成开发模式下的bundles以及启动本地server
"dev": "webpack-dev-server"
}
2.webpack-dev-server
我们每修改一次就要需要输入 npm run dev 是一件非常无聊的事情,幸运的是,我们可以把让他自己运行,那就是使用webpack-dev-server。
除了提供模块打包功能,Webpack还提供了一个基于Node.js Express框架的开发服务器,它是一个静态资源Web服务器,对于简单静态页面或者仅依赖于独立服务的前端页面,都可以直接使用这个开发服务器进行开发。在开发过程中,开发服务器会监听每一个文件的变化,进行实时打包,并且可以推送通知前端页面代码发生了变化,从而可以实现页面的自动刷新。
- 安装:
npm install --save-dev webpack-dev-server
- 调整npm的package.json中scripts 部分开发命令的配置
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"pub": "webpack --config webpack.pub.config.js",
"dev": "webpack-dev-server --config webpack.dev.config.js --devtool eval --progress --colors --hot --content-base src"
}
}
在dev的配置中做了以上改变之后,webpack-dev-server 会在 localhost:8080 建立一个 Web 服务器。
几个参数的解释:
--devtool eval:为你的代码创建源地址。当有任何报错的时候可以让你更加精确地定位到文件和行号
--progress:显示合并代码进度
--colors -- hot:命令行中显示颜色
--content-base 指向设置的输出目录
--手动访问 http://localhost:8080
简单来说,当你运行 npm run dev的时候,webpack会帮你会启动一个 Web 服务器,然后监听文件修改,然后自动重新合并你的代码。真的非常简洁。
注意点
用webpack-dev-server生成bundle.js文件是在内存中的,并没有实际生成;
如果引用的文件夹中已经有bundle.js就不会自动刷新了,你需要先把bundle.js文件手动删除(后期有插件可以完成);
用webstorm的同学注意了,因为webstorm是自动保存的,所以可能识别的比较慢,你需要手动的ctrl+s一下;
几个报错
- webpack版本的问题
如果webpack使用的1.x的版本,那么webpack-dev-server也要使用1.x的版本,否则会报如下错误:Connot find module 'webpack/bin/config-yargs'。 - 端口占用问题
如果已经有一个工程中使用了webpack-dev-server,并且在运行中,没有关掉的话,那么8080端口就被占用了,此时如果在另一个工程中使用webpack-dev-server就会报错:Error: listen EADDRINUSE 127.0.0.1:8080。
webpack-dev-server(有利于在开发模式下编译)
这是一个基于Express.js框架开发的web server,默认监听8080端口。server内部调用Webpack,这样做的好处是提供了额外的功能如热更新“Live Reload”以及热替换“Hot Module Replacement”(即HMR)。
webpack-dev-server的“hot” 和 “inline”选项
“inline”选项会为入口页面添加“热加载”功能,“hot”选项则开启“热替换(Hot Module Reloading)”,即尝试重新加载组件改变的部分(而不是重新加载整个页面)。如果两个参数都传入,当资源改变时,webpack-dev-server将会先尝试HRM(即热替换),如果失败则重新加载整个入口页面。
// 当资源发生改变,以下三种方式都会生成新的bundle,但是又有区别:
// 1. 不会刷新浏览器
$ webpack-dev-server
//2. 刷新浏览器
$ webpack-dev-server --inline
//3. 重新加载改变的部分,HRM失败则刷新页面
$ webpack-dev-server --inline --hot
3.“entry”:值分别是字符串、数组和对象的情况
- 数组类型
添加多个彼此不互相依赖的文件,你可以使用数组格式的值。 - 对象
现在,假设你的应用是多页面的(multi-page application)而不是SPA,有多个html文件(index.html和profile.html)。然后你通过一个对象告诉Webpack为每一个html生成一个bundle文件。
以下的配置将会生成两个js文件:indexEntry.js和profileEntry.js分别会在index.html和profile.html中被引用。 - 混合类型
enter对象里使用数组类型,例如下面的配置将会生成3个文件:vender.js(包含三个文件),index.js和profile.js文件。
4. output:“path”项和“publicPath”项output项
告诉webpack怎样存储输出结果以及存储到哪里。output的两个配置项“path”和“publicPath”可能会造成困惑。
“path”仅仅告诉Webpack结果存储在哪里,然而“publicPath”项则被许多Webpack的插件用于在生产模式下更新内嵌到css、html文件里的url值。
5..babelrc 文件
babal-loader使用”presets“配置项来标识如何将ES6语法转成ES5以及如何转换React的JSX成js文件。我们可以用如下的方式使用”query“参数传入配置:
module: {
loaders: [ { test: /\.jsx?$/,
exclude: /(node_modulesbower_components)/,
loader: 'babel',
query: { presets: ['react', 'es2015'] } } ]
}
然而在很多项目里babal的配置可能比较大,因此你可以把babal-loader的配置项单独保存在一个名为”.babelrc“的文件中,在执行时babal-loader将会自动加载.babelrc文件。
所以在很多例子里,你可能会看到:
//webpack.config.js module:
{
loaders: [ { test: /\.jsx?$/,
exclude: /(node_modulesbower_components)/,
loader: 'babel' } ]
}
//.bablerc
{ presets: ['react', 'es2015'] }
6.plugin插件
插件一般都是用于输出bundle的node模块。
例如,uglifyJSPlugin获取bundle.js然后压缩和混淆内容以减小文件体积。
类似的extract-text-webpack-plugin内部使用css-loader和style-loader来收集所有的css到一个地方最终将结果提取结果到一个独立的”styles.css“文件,并且在html里边引用style.css文件。
//webpack.config.js
// 获取所有的.css文件,合并它们的内容然后提取css内容到一个独立的”styles.css“里
var ETP = require("extract-text-webpack-plugin");
module: {
loaders: [ {
test: /\.css$/,
loader:ETP.extract("style-loader","css-loader") }
] },
plugins: [ new ExtractTextPlugin("styles.css") //Extract to styles.css file ]
}
注意:如果你只是想把css使用style标签内联到html里,你不必使用extract-text-webpack-plugin,仅仅使用css loader和style loader即可:
module: {
loaders: [{
test: /\.css$/,
loader: 'style!css' // (short for style-loader!css-loader)
}]
7.加载器和插件
加载器就是webpack准备的一些预处理工具,比如编译jsx和es6的加载器,处理sass等....
使用加载器的步骤也很简单,首先是安装依赖,然后在配置文件的module中加一个字段module字段,在module写上loaders,在loaders中写上相应的配置。
常用加载器:
- 编译jsx和ES6到原生js
安装以下的依赖
npm install --save-dev babel-loader babel-core babel-preset-es2015 babel-preset-react
修改开发配置文件
module: {
loaders: [
{
test: /\.jsx?$/, // 用正则来匹配文件路径,这段意思是匹配 js 或者 jsx
loader: 'babel',// 加载模块 "babel" 是 "babel-loader" 的缩写
query: {
presets: ['es2015', 'react']
}
}
]
}
- 加载CSS
加载 CSS 需要 css-loader 和 style-loader,他们做两件不同的事情,css-loader会遍历 CSS 文件,然后找到 url() 表达式然后处理他们,style-loader 会把原来的 CSS 代码插入页面中的一个 style 标签中。
Loader处理单独的文件级别并且通常作用于包生成之前或生成的过程中。
而插件则是处理包(bundle)或者chunk级别,且通常是bundle生成的最后阶段。一些插件如commonschunkplugin甚至更直接修改bundle的生成方式。
四、webpack的特点
- 对 CommonJS 、AMD 、ES6的语法做了兼容;
- 对js、css、图片等资源文件都支持打包;
- 串联式 模块加载器 以及 插件机制 ,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持;
- 有独立的配置文件webpack.config.js;
- 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间;
- 支持 SourceUrls 和 SourceMaps,易于调试;
- 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活;
- webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快;
webpack最常用与spa应用,主要是vue和React,其实它就非常像Browserify,但是将应用打包为多个文件。如果单页面应用有多个页面,那么用户只从下载对应页面的代码. 当他么访问到另一个页面, 他们不需要重新下载通用的代码。