大概几个月前,刚接触 gulp 的时候,经过 gulp 对前端工做流进行优化,在 gulpfile.js 文件中写插件,编译 less、stylus,压缩 css、js 等等,感受工做效率获得极大的提高,本来手动的东西,如今都是自动化了。javascript
可是,如今,学习 react 的时候,我又不得不来学习 webpack。webpack 最近火得不行(其实已经火了好久了),当下最热门的前端资源模块化管理和打包工具,它能把各类资源,包括 jxs、coffeeJS、less/sass,甚至图片,看成模块来加载和使用。一样它须要一个 webpack.config.js 的配置文件,有专门针对于 css、js 和图片等插件,在 js 中直接经过 require 来使用模块,很方便。css
webpack 能够将文件模块按照依赖打包成方便使用的前端资源,还能够将按需加载的模块进行异步加载。html
ps:gulp/grunt 是前端构建工具,是自动化工具,能够简化前端流程;而 webpack 是前端模块化方案。虽然两者在某些功能上有共同点(好比压缩代码),但本质是不同的,好比如今还有基于 gulp 的 webpack 插件gulp-webpack。前端
前端模块化是大势所趋,随着 webapp 的兴起,浏览器的功能愈来愈强大,而一个单页面在使用的过程当中会加载更多的 JavaScript 代码,这给前端开发的流程和运行带来了很大的挑战。java
时下,已经有很多前端模块化系统:node
script 标签,这是最传统的文件模块,一个文件是一个模块,react
<script src="./main.js"></script> <script src="./module.js"></script>
这些接口会暴露在全局做用下,弊端不少:webpack
全局做用域形成变量冲突git
文件只能按照 script 的顺序进行加载程序员
开发人员必须主观解决代码库和模块的依赖关系
在大型的项目中,会形成资源文件难以管理,代码库混乱不堪
CommonJS
NodeJS 遵循的就是 CommonJS 规范,这种经过 require 和 module.exports 的方式很常见:
//main.js var module = require('./module.js') // dosomething module.exports = somevalue;
优势是服务器端模块便于重用,简单易用,且 NPM 上有几十万个可使用的模块包,缺点是这种加载方式属于同步加载,不适用于浏览器,且不能非阻塞的加载多个模块。关于 CommonJS 循环加载的原理,能够看看这篇文章中的介绍 ES6模块加载的实质 CommonJS 部分。
AMD CMD
AMD 是适合在浏览器中的异步加载的模块,
define("module", ["dep1", "dep2"], function(d1, d2) { return someExportedValue; }); require(["module", "../file"], function(module, file) { /* ... */ });
CMD 规范和 AMD 规范很像,CommonJS 规范保持了很大的兼容。
还有就是 ES6 中的模块加载系统,详情请移步ES6模块加载。
在上面的分析中,提到的都只是对 JavaScript 文件等加载,然而在前端的开发中还须要对图片,样式,字体文件和 html 模版等样式的加载,webpack 可让这一切成为可能:
require("./style.css"); require("./style.less"); require("./template.jade"); require("./image.png");
在加载的过程当中,还能经过对静态文件的分析,好比 css,把它内联到 html 的 style 样式中。
webpack 是集大成者,支持 CommonJS,AMD/CMD,还能对图片,样式等进行模块化。
好比直接使用 CommonJS 语法:
var m1 = require('module1'); var m2 = require('module2'); //dosomething module.exports = function(){ m1(); m2(); }
经过 npm 全局安装 webpack,npm install webpack -g
,也能够在本地项目中安装依赖 npm install webpack --save-dev
,前提要确保 npm init。
目录下面有一个静态页面 index.html 和 JS 入口文件 entry.js,
<!-- index.html --> <html> <head> <meta charset="utf-8"> </head> <body> <script src="bundle.js"></script> </body> </html> // entry.js document.write('hello webpack');
而后使用命令 webpack entry.js bundle.js
就能够生成 bundle.js 文件。
如今添加 module.js 文件,在 ertry.js 中引用:
//module.js document.write('hello module'); //ertry.js document.write(require('module'));
webpack 会分析每一个文件的入口,把依赖的相关文件都打包到 bundle.js。webpack entry.js bundle.js
这句话执行后,会先执行 entry.js,其余文件,则只有在 require 的时候才会加载。
还能够经过插件来加载样式模块。目录下添加一个 style.css 文件,安装 css 和 style 模块 npm install css-loader style-loader
:
/* style.css */ body { background: yellow; } // entry.js require("!style!css!./style.css"); document.write('hello webpack');
这个时候打包,刷新页面,就能够看到 index.html 中内联的 css 样式。
前面介绍的这种是命令行打包的方式,比较麻烦,通常都是写一个 webpack 的配置文件,上面的配置文件能够以下:
//webpack.config.js var webpack = require('webpack') module.exports = { entry: './entry.js', output: { path: __dirname, filename: 'bundle.js' }, module: { loaders: [ {test: /\.css$/, loader: 'style!css'} ] } }
module.loader 中 loader 可能有人会有疑惑,这种用感叹号把模块放开表示 css 文件加载多个模块,加载的顺序从右到左,先加载 css 模块,再加载 style 模块。在 entry.js 文件中就能够简化的写成 require('./style.css')
。
配置文件的几个比较重要的参数以下:
entry: 编译过程的输入
output: 编译过程的输出
module: 模块module的处理方式
plugin: 配置文件的插件入口
resolve 配置文件其余解决方案
output 表明输出,path 表明路径,filename 表明文件名。plugin 表示插件,有内置插件和扩展插件,
var webpack = require("webpack"); var ComponentPlugin = require("component-webpack-plugin"); module.exports = { plugin: [ //内置压缩插件 new webpack.optimize.UglifyJsPlugin({ compressor: { warnings: false, }, }), // 扩展插件 ComponentPlugin() ] };
更多插件能够去官网查看。
module 中最须要注意的就是 loaders,
test:正则表达式,用于匹配文件名(必须)
loader:须要加载的 loaders 列表,可用 ! 加载多个(必须)
include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不须要处理的文件(文件夹)(可选);
query:为loaders提供额外的设置选项(可选)
resolve 是配置的其余解决方案,好比 resolve.alias 能够定义模块的别名,resolve.root 能够定义绝对路径。resolve.extensions 能够省去加载文件的后缀名,即后缀名自动补全。可是必需要在前面加一个空的字符串,不然会致使没法加载的状况。
webpack-dev-server 是一个专门为 webpack 服务的 nodejs 服务器,经过 npm install --save-dev webpack-dev-server
命令来安装。
// webpack.config.js devServer: { contentBase: "./", //本地服务器所加载的页面所在的目录 colors: true, //终端中输出结果为彩色 historyApiFallback: true, //不跳转 inline: true //实时刷新 }
Babel 是一个编译 JavaScript 的平台,它的功能很是强大,可用编译 JSX,ES6,ES7,生成浏览器识别的 JavaScript 语言。须要安装多个依赖:npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react
。
//webpack.config.js module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel', query: { presets: ['es2015','react'] } } ] },
webpack 提供热更新,官网关于热更新的介绍:hot-module-replacement-with-webpack,webpack-dev-server。
webpack-dev-server 自己就带有热更新功能,只须要在参数启动参数重添加 webpack-dev-server --inline --hot
,若是嫌每次添加麻烦,可用在 package.json 中 script 设置 start 或在 webpack.config.js 中开启。
这个插件主要是针对于 html 的,能够自动生成 html 文件,尤为当使用了 hash 以后,不用困扰因 hash 的变化带来的问题。
var webpack = require('webpack') var htmlwebpackplugin = require('html-webpack-plugin') module.exports = { entry: './main.js', output: { path: __dirname, filename: 'bundle.js' }, plugins: [new htmlwebpackplugin()] }
有时候会由于 html 中必需要有一些特别的东西,不能直接生成,此时就须要配置模版:
module.exports = { plugins:[ new htmlwebpackplugin({ filename: 'hello.html', // 生成的文件 template: 'src/template.html' // 模版文件 }) ] }
更多配置信息,参考:
title: 页面 title
filename: 输出的 HTML 文件名,默认是 index.html
template: 模板文件路径,支持加载器,好比 html!./index.html
inject: true | 'head' | 'body' | false ,注入全部的资源到特定的 template 或者 templateContent 中,若是设置为 true 或者 body,全部的 javascript 资源将被放置到 body 元素的底部,'head' 将放置到 head 元素中。
favicon: 添加特定的 favicon 路径到输出的 HTML 文件中。
minify: {} | false , 传递 html-minifier 选项给 minify 输出
hash: true | false, 若是为 true, 将添加一个惟一的 webpack 编译 hash 到全部包含的脚本和 CSS 文件,对于解除 cache 颇有用。
cache: true | false,若是为 true, 这是默认值,仅仅在文件修改以后才会发布文件。
showErrors: true | false, 若是为 true, 这是默认值,错误信息会写入到 HTML 页面中
chunks: 容许只添加某些块 (好比,仅仅 unit test 块)
chunksSortMode: 容许控制块在添加到页面以前的排序方式,支持的值:'none' | 'default' | {function}-default:'auto'
excludeChunks: 容许跳过某些块,(好比,跳过单元测试的块)
若是你感兴趣,还能够去看一看如何本身手动写 webpack 插件。连接1连接2
有时候,一些函数只需在 dev 环境下运行,有些函数要在 product 环境下运行,经过设置 webpack 的 DefinePlugin 就能够很轻松的帮助咱们实现,好比:
var webpack = require('webpack'); var devFlagPlugin = new webpack.DefinePlugin({ // __DEV__ 默认是 false,除非手动设置开发环境 __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false')) }); module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, plugins: [devFlagPlugin] };
// main.js document.write('<h1>Hello World</h1>'); if (__DEV__) { //若是开发环境 document.write(new Date()); }
此时有两种运行方式,进入开发模式的运行方式:
# Linux & Mac $ env DEBUG=true webpack-dev-server # Windows $ set DEBUG=true $ webpack-dev-server
在接触 webpack 以前,强烈建议先学习 ES6。其实,不少人都说前端变化太快,昨天还很火热的框架,可能今天就被另外一个所取代。我以为,正是这种快速的更新,让那些喜欢学习,喜欢钻研的程序员,得到了新生,新的活力。共勉!
webpack 中文官网
阮一峰 webpack-demos
webpack编译流程漫谈
webpack学习之路
入门Webpack,看这篇就够了
一小时包教会 —— webpack 入门指南
欢迎来个人博客讨论。