p,pre,span,div,code,td span,th span { font-size: 16px }
一、利用package.json执行打包任务
首先使用npm init生成package.json文件;然后配置scripts如下:
"scripts": { "build":"webpack", "start": "webpack-dev-server --hot --inline" }
使用npm run build就可以编译文件,使用npm start就可以启动服务器。
npm的start
是一个特殊的脚本名称,它的特殊性表现在,在命令行中使用npm start
就可以执行相关命令,如果对应的此脚本名称不是start
,想要在命令行中运行时,需要这样用npm run {script name}
如npm run build。
二、常用设置
2.1 entry
entry
有三种写法,每个入口称为一个chunk。
- 字符串:
entry: "./index/index.js"
:配置模块会被解析为模块,并在启动时加载。chunk名为默认为main
, 具体打包文件名视output
配置而定 - 数组
entry: [‘./src/mod1.js‘, [...,] ‘./src/index.js‘]
:所有的模块会在启动时 按照配置顺序 加载,合并到最后一个模块会被导出。chunk名默认为main.使用数组形式仍然只生成一个bundle.js,但是bundle.js中加载顺序是按照数组中规定的顺序。
- 对象
entry: {index: ‘...‘, login : [...] }
:如果传入Object,则会生成多个入口打包文件, key是chunk名,value可以是字符串,也可是数组。
2.2 output
设置入口配置的文件的输出规则,通过output
对象实现,常用设置:
output: { path: __dirname + ‘/build‘, filename: ‘[name]-[id].js‘, publicPath: ‘/asstes/‘ }
output.path
:指定输出文件路径,通常设置为__dirname + ‘/build’,“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录。output.filename
: 输出文件名称,有下面列出的四种可选的变量。 filename项的配置可以是这几种的任意一种或多种的组合。 如 output.filename = ‘[name]-[id].js’, 则输出就是 index-1.js、 login-2.js。[id]
, chunk的id[name]
,chunk名[hash]
, 编译哈希值[chunkhash]
, chunk的hash值
output.publicPath
:设置为想要的资源访问路径。访问时,则需要通过类似http://localhost:8080/asstes/index-1.js
来访问资源,如果没有设置,则默认从站点根目录加载。
path vs publicPath
“path”仅仅告诉Webpack结果存储在哪里,然而“publicPath”项则被许多Webpack的插件用于在生产模式下更新内嵌到css、html文件里的url值。
例如,在localhost(译者注:即本地开发模式)里的css文件中你可能用“./test.png”这样的url来加载图片,但是在生产模式下“test.png”文件可能会定位到CDN上并且你的Node.js服务器可能是运行在HeroKu上边的。这就意味着在生产环境你必须手动更新所有文件里的url为CDN的路径。
然而你也可以使用Webpack的“publicPath”选项和一些插件来在生产模式下编译输出文件时自动更新这些url。
// 开发环境:Server和图片都是在localhost(域名)下 .image { background-image: url(‘./test.png‘); } // 生产环境:Server部署下HeroKu但是图片在CDN上 .image { background-image: url(‘https://someCDN/test.png‘); }
2.3 loader
Loaders需要单独安装并且需要在webpack.config.js下的modules
关键字下进行配置,Loaders的配置选项包括以下几方面:
test
:一个匹配loaders所处理的文件的拓展名的正则表达式(必须)loader
:loader的名称(必须)include/exclude
:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);query
:为loaders提供额外的设置选项(可选)
1)安装loader
npm install xxx-loader --save-dev
其中,XXX为webpack支持的loader名,常用的有:html、css、jsx、coffee、jade、less、sass、style等。
你可以通过webpack loader 列表 查看所有支持的loader。
2)配置loader
modules: { loaders: [ { test: /\.js$/, //匹配希望处理文件的路径 exclude: /node_modules/, // 匹配不希望处理文件的路径 loaders: ‘xxx-loader?a=x&b=y‘ //此处xxx-loader 可以简写成xxx , ?后以query方式传递给loader参数 }, ... ] }
多个loader可以用在同一个文件上并且被链式调用。链式调用时从右到左执行且loader之间用“!”来分割。
module:{ loaders: [ { test: /\.less$/, loader: "style!css!less" } ] }
先使用less-loader处理.less文件,然后将输出结果传递给css-loader继续解析,css-loader用于读取CSS文件,再将解析结果传递给style-loader解析,style-loader用于将样式内嵌到HTML中。
下面介绍几个常用的loader:
2.3.1 babel
Babel其实是一个编译JavaScript的平台,它的强大之处表现在可以通过编译帮你达到以下目的:
- 下一代的JavaScript标准(ES6,ES7),这些标准目前并未被当前的浏览器完全的支持;
- 使用基于JavaScript进行了拓展的语言,比如React的JSX
1)babel的安装
npm install babel-core babel-loader babel-preset-es2015 babel-preset-react --save-dev
2)配置
module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: ‘babel‘,//在webpack的module部分的loaders里进行配置即可 query: { presets: [‘es2015‘,‘react‘] } } ] }
Babel其实可以完全在webpack.config.js中进行配置,但是考虑到babel具有非常多的配置选项,在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把babel的配置选项放在一个单独的名为 ".babelrc" 的配置文件中。我们现在的babel的配置并不算复杂,不过之后我们会再加一些东西,因此现在我们就提取出相关部分,分两个配置文件进行配置(webpack会自动调用.babelrc
里的babel配置选项),如下:
//.babelrc { "presets": ["react", "es2015"] }
2.3.2 css-loader style-loader
webpack提供两个工具处理样式表,css-loader
和 style-loader
,二者处理的任务不同,css-loader
使你能够使用类似@import
和 url(...)
的方法实现 require()
的功能也就是读取CSS文件,style-loader
将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。
1)安装
npm install --save-dev style-loader css-loader
2)配置
module: { loaders: [ { test: /\.css$/, loader: ‘style!css‘//添加对样式表的处理 } ] }
webpack只有单一的入口,其它的模块需要通过 import, require, url等导入相关位置,为了让webpack能找到”main.css“文件,我们把它导入”main.js “中,如下
//main.js import React from ‘react‘; import {render} from ‘react-dom‘; import Greeter from ‘./Greeter‘; import ‘./main.css‘;//使用require导入css文件 render(<Greeter />, document.getElementById(‘root‘));
2.3.3 postcss-loader
我们使用PostCSS来为CSS代码自动添加适应不同浏览器的CSS前缀。
1)安装
npm install postcss-loader autoprefixer --save-dev
2)配置
在webpack配置文件中进行设置,只需要新建一个postcss关键字,并在里面申明依赖的插件,如下,现在你写的css会自动根据添加不同前缀了。
// webpack.config.js ... loaders:[ { test: /\.css$/, loader: ‘style!css?modules!postcss‘ } ] postcss: [ require(‘autoprefixer‘)//调用autoprefixer插件 ],
2.3.4 url-loader
在css中或者js逻辑中,都会涉及到require图片的情况,webpack可以内联图片地址到打包js中并且通过require()
返回图片路径。当然,不只是图片,还有css中用到的iconfont,特殊情况用到的flash等,都可以相似处理。这里,我们需要用到url-loader 或 file-loader。
file-loader
: 将匹配到的文件复制到输出文件夹,并根据output.publicPath的设置返回文件路径url-loader
: 类似file-loader ,但是它可以返回一个DataUrl (base 64)如果文件小于设置的限制值limit
。
1)安装
npm install url-loader file-loader --save-dev
2)配置
module:{ loaders:[ { test: /\.(png|jpg)$/, loader: ‘url-loader?limit=8192‘ // <= 8kb的图片base64内联 }, { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: ‘url?limit=10000&minetype=application/font-woff‘ }, { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: ‘url?limit=10&minetype=application/font-woff‘ }, { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: ‘url?limit=10&minetype=application/octet-stream‘ }, { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: ‘file‘ }, { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: ‘url?limit=10&minetype=image/svg+xml‘ } ] }
url-loader用来转换image文件,如果图像大小小于8192比特,就转换成data url。否则转换成正常的URL。问号?用来向loader传递参数。
<img src="data:image/png;base64,iVBOR...uQmCC"> 小图像
<img src="4853ca667a2b8b8844eb2693ac1b2578.png"> 大图像
2.3.5 bundle-loader
对于大型网站,将所有代码放在一个文件里面会效率低,webpack允许将文件分成多个chunks。尤其是如果某些代码块只在某些情况下需要,这些chunks就可以按需加载。有两种方式可以定义分割点:
方式一:require.ensure
使用require.ensure可以定义一个分割点,在本例中require.ensure告诉webpack文件a.js需要和bundle.js分开,成为一个单独的chunk文件。不需要改变index.html和webpack.config.js
// main.js require.ensure([‘./a‘], function(require) { var content = require(‘./a‘); console.log(content); document.open(); document.write(‘<h1>‘ + content + ‘</h1>‘); document.close(); }); // a.js module.exports = ‘Hello World‘;
现在webpack会考虑这些依赖项,输出文件和运行文件,你不需要在index.html和webpack.config.js中提及这些文件。
在网页中你不会感觉到任何不同。然而,webpack实际将main.js和a.js编译进不同的chunks中(bundle.js和1.bundle.js),然后在需要的时候从bundle.js中加载1.bundle.js。
方式二:bundle-loader
1)安装
npm install bundle-loader --save-dev
2)配置
不需要在webpack.config.js配置,直接在main.js中使用require("bundle!./a.js")加载需要分离的文件
// main.js var load=require(‘bundle!./a.js‘); load(function(file){ document.open(); document.write("<h1>"+file+"</h1>"); document.close(); }) // a.js module.exports="hello world";
仍然生成bundle.js和1.bundle.js文件
2.4 pllugin
Loaders和Plugins常常被弄混,但是他们其实是完全不同的东西,可以这么来说,loaders是在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个,插件并不直接操作单个文件,它直接对整个构建过程其作用。
Webpack有很多内置插件,同时也有很多第三方插件,可以让我们完成更加丰富的功能。
如果使用webpack自身的组件,在本地安装webpack
npm install webpack --save-dev
就可以在webpack.config.js文件配置中的plugins关键字部分添加该插件的一个实例(plugins是一个数组)
plugins: [ new webpack.BannerPlugin("Copyright Flying Unicorns inc.")//在这个数组中new一个就可以了 ],
如果使用的是第三方插件,则需要npm install第三方插件后才能使用。
下面介绍几个常见的插件:
2.4.1 html-webpack-plugin open-browser-webpack-plugin
html-webpack-plugin用于自动生成index.html文件,filename用来指定文件名和路径,注意这里的路径是相对于output的path路径的,即默认情况下生成的index.html在output中规定的path路径下,可以使用__dirname指向当前目录。这个插件的作用是依据一个简单的模板,帮你生成最终的Html5文件,这个文件中自动引用了你打包后的JS文件。每次编译都在文件名中插入一个不同的哈希值。
open-browser-webpack-plugin用于自动打开浏览器
这两个都是第三方插件,因此需要安装下载
npm install html-webpack-plugin open-browser-webpack-plugin --save-dev
1)安装
npm install html-webpack-plugin open-browser-webpack-plugin --save-dev
2)配置
var HtmlwebpackPlugin=require("html-webpack-plugin"); var OpenBrowserPlugin=require("open-browser-webpack-plugin"); module.exports={ entry:"./src/main.js", output:{ path:"./build", filename:"bundle.js" }, plugins:[ new HtmlwebpackPlugin({ title:"webpackhtml", template:"./src/index.temp.html", // 模板文件 filename:__dirname+"/index.html" }), new OpenBrowserPlugin({ url:"http://localhost:8080" }) ] } // index.temp.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>commons-chunk-plugin</title> </head> <body> <div id="a"></div> <div id="b"></div> </html> // 自动生成的index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>commons-chunk-plugin</title> </head> <body> <div id="a"></div> <div id="b"></div> </html> <script type="text/javascript" src="build/init.js"></script> <script type="text/javascript" src="build/bundl2.js"></script> <script type="text/javascript" src="build/bundl1.js"></script>
2.4.2 优化插件
webpack提供了一些在发布阶段非常有用的优化插件,它们大多来自于webpack社区,可以通过npm安装,通过以下插件可以完成产品发布阶段所需的功能
- OccurenceOrderPlugin :为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
- UglifyJsPlugin:获取bundle.js然后压缩和混淆内容以减小文件体积。
- ExtractTextPlugin:分离CSS和JS文件,内部使用css-loader和style-loader来收集所有的css到一个地方最终将结果提取结果到一个独立的”styles.css“文件,并且在html里边引用style.css文件。
1)安装
npm install extract-text-webpack-plugin --save-dev
2)配置
var webpack = require(‘webpack‘); var ExtractTextPlugin = require(‘extract-text-webpack-plugin‘); module.exports = { entry: __dirname + "/app/main.js", output: { path: __dirname + "/build", filename: "bundle.js" }, module: { loaders: [ { test: /\.css$/, loader: ExtractTextPlugin.extract(‘style-loader‘, ‘css-loader?modules!postcss-loader‘) // 获取所有的.css文件,并提取到指定的css文件中 } ] }, postcss: [ require(‘autoprefixer‘) ], plugins: [ new webpack.optimize.OccurenceOrderPlugin(), new webpack.optimize.UglifyJsPlugin({ compress:{ warnings:false } }), new ExtractTextPlugin("style.css") // 提取到style.css文件中 ] }
注意:如果你只是想把css使用style标签内联到html里,你不必使用extract-text-webpack-plugin,仅仅使用css loader和style loader即可:
缓存
缓存无处不在,使用缓存的最好方法是保证你的文件名和文件内容是匹配的(内容改变,名称相应改变)
webpack可以把一个哈希值添加到打包的文件名中,使用方法如下,添加特殊的字符串混合体([name], [id] and [hash])到输出文件名前
var webpack = require(‘webpack‘); var HtmlWebpackPlugin = require(‘html-webpack-plugin‘); var ExtractTextPlugin = require(‘extract-text-webpack-plugin‘); module.exports = { entry: __dirname + "/app/main.js", output: { path: __dirname + "/build", filename: "[name]-[hash].js" }, module: { loaders: [ { test: /\.css$/, loader: ExtractTextPlugin.extract(‘style‘, ‘css?modules!postcss‘) } ] }, postcss: [ require(‘autoprefixer‘) ], plugins: [ new webpack.optimize.OccurenceOrderPlugin(), new webpack.optimize.UglifyJsPlugin(), new ExtractTextPlugin("[name]-[hash].css") ] }
现在用户会有合理的缓存了。
2.4.3 CommonsChunkPlugin
提取公共部分
var webpack=require("webpack"); var commonChunk=webpack.optimize.CommonsChunkPlugin; module.exports={ entry:{ bundl1:"./src/main1.js", bundl2:"./src/main2.js" }, output:{ path:__dirname+"/build", filename:"[name].js" }, module:{ loaders:[ { test:/\.js[x]?$/, exclude:/node_modules/, loader:"babel-loader", query:{ presets:["es2015","react"] } } ] }, plugins:[ new commonChunk("init.js") ] }
2.4.4 Hot Module Replacement
Hot Module Replacement可以在不重新加载网页的同时实现应用的被改变部分的实时交换,添加或者移除modules。有两种方法可以开启HMR:
1.在命令行使用--hot和--inline
webpack-dev-server --hot --inline
--hot:添加HotModuleReplacementPlugin并打开服务器的hot模式
--inline:embed the webpack-dev-server runtime into the bundle.
--hot --inline:也添加webpack/hot/dev-server入口
2.修改webpack.config.js文件,在plugins区域添加new webpack.HotModuleReplacementPlugin(),
添加webpack/hot/dev-server和webpack-dev-server/client?http://localhost:8080到入口文件区域
webpack.config.js类似下面的写法:
var webpack = require(‘webpack‘); var path = require(‘path‘); module.exports = { entry: [ ‘webpack/hot/dev-server‘, ‘webpack-dev-server/client?http://localhost:8080‘, ‘./index.js‘ ], output: { filename: ‘bundle.js‘, publicPath: ‘/static/‘ }, plugins: [ new webpack.HotModuleReplacementPlugin() ], module: { loaders: [{ test: /\.jsx?$/, exclude: /node_modules/, loader: ‘babel-loader‘, query: { presets: [‘es2015‘, ‘react‘] }, include: path.join(__dirname, ‘.‘) }] } };
三、其他配置
3.1 externals
如果引入一个库想要在全局使用,那么
1.在index.html中用script标签将库引入
2.在webpack.config.js文件中使用externals:{自定义库名:库名}
3.在需要使用库的地方使用require("自定义库名")引入就可以使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script src="src/data.js"></script> <script src="bundle1.js"></script> <script src="bundle2.js"></script> </body> </html>
// webpack.config.js module.exports={ entry:{ bundle1:"./src/main1.js", bundle2:"./src/main2.js" }, output:{ path:__dirname+"/build", filename:"[name].js" }, externals:{ "datasrc":"data" // 直接使用require("datasrc")就可以引入该库 } }
// main1.js var data=require("datasrc"); document.write("main1.js"); document.write(data); console.log(data); // main2.js var data=require("datasrc"); document.write("main2.js"); document.write(data); // data.js var data="hello world";
3.2 处理文件扩展名
很多Webpack的配置文件都有一个resolve
属性,然后就像下面代码所示有一个空字符串的值。空字符串在此是为了resolve一些在import文件时不带文件扩展名的表达式,如require(‘./myJSFile‘)
或者import myJSFile from ‘./myJSFile‘
(实际就是自动添加后缀,默认是当成js文件来查找路径)
{ resolve: { extensions: [‘‘, ‘.js‘, ‘.jsx‘] } }
四、生成source map
打包后的文件有时候你是不容易找到出错了的地方对应的源代码的位置的,Source Maps就是来帮我们解决这个问题的。
通过简单的配置后,Webpack在打包时可以为我们生成的source maps,这为我们提供了一种对应编译文件和源文件的方法,使得编译后的代码可读性更高,也更容易调试。
在webpack的配置文件中配置source maps,需要配置devtool
,它有以下四种不同的配置选项,各具优缺点,描述如下:
devtool选项 | 配置结果 |
source-map | 在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包文件的构建速度; |
cheap-module-source-map | 在一个单独的文件中生成一个不带列映射的map,不带列映射提高项目构建速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便; |
eval-source-map | 使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。不过在开发阶段这是一个非常好的选项,但是在生产阶段一定不要用这个选项; |
cheap-module-eval-source-map | 这是在打包文件时最快的生成source map的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map 选项具有相似的缺点; |
正如上表所述,上述选项由上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的构建速度的后果就是对打包后的文件的的执行有一定影响。
在学习阶段以及在小到中性的项目上,eval-source-map
是一个很好的选项,不过记得只在开发阶段使用它,继续上面的例子,进行如下配置
module.exports = { devtool: ‘eval-source-map‘,//配置生成Source Maps,选择合适的选项 entry: __dirname + "/app/main.js", output: { path: __dirname + "/public", filename: "bundle.js" } }
五、构建本地服务器
想不想让你的浏览器监测你都代码的修改,并自动刷新修改后的结果,其实Webpack提供一个可选的本地开发服务器,这个本地服务器基于node.js构建,可以实现你想要的这些功能,不过它是一个单独的组件,在webpack中进行配置之前需要单独安装它作为项目依赖
npm install webpack-dev-server --save-dev
devserver作为webpack配置选项中的一项,具有以下配置选项
devserver配置选项 | 功能描述 |
contentBase | 默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“public"目录) |
port | 设置默认监听端口,如果省略,默认为”8080“ |
inline | 设置为true ,当源文件改变时会自动刷新页面 |
colors | 设置为true ,使终端输出的文件为彩色的 |
historyApiFallback | 在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true ,所有的跳转将指向index.html |
继续把这些命令加到webpack的配置文件中,现在的配置文件如下所示
module.exports = { devtool: ‘eval-source-map‘, entry: __dirname + "/app/main.js", output: { path: __dirname + "/public", filename: "bundle.js" }, devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 colors: true,//终端中输出结果为彩色 historyApiFallback: true,//不跳转 inline: true//实时刷新 } }