首先要安装 Node.js, Node.js 自带了软件包管理器 npm。用 npm 全局安装 Webpack:
$ npm install webpack -g
通常我们会将 Webpack 安装到项目的依赖中,这样就可以使用项目本地版本的 Webpack。
# 进入项目目录,初始化,创建 package.json。 # 若存在 package.json 文件,则不运行。 $ npm init # 确定已经有 package.json # 安装 webpack 依赖 $ npm install webpack --save-dev
如果需要使用 Webpack 开发工具,要单独安装:
$ npm install webpack-dev-server --save-dev
首先创建一个静态页面 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('It works.')
然后编译 entry.js
并打包到 bundle.js
:
$ webpack entry.js bundle.js
用浏览器打开 index.html
将会看到
It works.
最终目录结构如下:
. ├── entry.js ├── index.html ├── package.json ├── node_modules
接下来添加一个模块 module.js
并修改入口 entry.js
:
// module.js module.exports = 'It works from module.js.'
// entry.js document.write('It works.') document.write(require('./module.js')) // 添加模块
重新打包 webpack entry.js bundle.js
后刷新页面看到变化
It works.It works from module.js.
最终目录结构如下:
. ├── bundle.js ├── entry.js ├── index.html ├── module.js ├── package.json ├── node_modules
Webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。Loader 可以理解为是模块和资源的转换器,它本身是一个函数,接受源文件作为参数,返回转换的结果。这样,我们就可以通过 require 来加载任何类型的模块或文件,比如 CoffeeScript、 JSX、 LESS 或图片。
先来看看 loader 有哪些特性?
Loader 可以通过管道方式链式调用,每个 loader 可以把资源转换成任意格式并传递给下一个 loader ,但是最后一个 loader 必须返回 JavaScript。
Loader 可以同步或异步执行。
Loader 运行在 node.js 环境中,所以可以做任何可能的事情。
Loader 可以接受参数,以此来传递配置项给 loader。
Loader 可以通过文件扩展名(或正则表达式)绑定给不同类型的文件。
Loader 可以通过 npm 发布和安装。
除了通过 package.json 的 main 指定,通常的模块也可以导出一个 loader 来使用。
Loader 可以访问配置。
插件可以让 loader 拥有更多特性。
Loader 可以分发出附加的任意文件。
Loader 本身也是运行在 node.js 环境中的 JavaScript 模块,它通常会返回一个函数。大多数情况下,我们通过 npm 来管理 loader,但是你也可以在项目中自己写 loader 模块。
按照惯例,而非必须,loader 一般以 xxx-loader
的方式命名, xxx
代表了这个 loader 要做的转换功能,比如 json-loader
。
除了npm安装模块的时候以外,在任何场景下,loader名字都是可以简写的。例如:安装时必须用全名,即: npm install json-loader
,而在引用 loader 的时候可以使用全名 json-loader
,也可以使用短名 json
。这个命名规则和搜索优先级顺序在 webpack 的 resolveLoader.moduleTemplates
api 中定义。
Default: ["*-webpack-loader", "*-web-loader", "*-loader", "*"]
Loader 可以在 require()
引用模块的时候添加,也可以在 webpack 全局配置中进行绑定,还可以通过命令行的方式使用。
loader是可以串联使用的,也就是说,一个文件可以先经过A-loader再经过B-loader最后再经过C-loader处理。而在经过所有的loader处理之前,webpack会先取到文件内容交给第一个loader。
接上一节的例子,我们要在页面中引入一个 CSS 文件 style.css
,首先将 style.css
也看成是一个模块,然后用 css-loader
来读取处理(路径处理、import处理等),然后经过 style-loader
处理(包装成JS文件,运行的时候直接将样式插入DOM中)。
/* style.css */ body { background: yellow; }
修改 entry.js
:
// entry.js require("!style!css!./style.css") // 载入 style.css document.write('It works.') document.write(require('./module.js'))
安装 loader:
# css-loader:读取 css 文件 # style-loader:将 css 文件插入页面 $ npm install css-loader style-loader --save-dev
重新编译打包,刷新页面,就可以看到黄色的页面背景了。
如果每次 require CSS 文件的时候都要写 loader 前缀,是一件很繁琐的事情。我们可以根据模块类型(扩展名)来自动绑定需要的 loader。
将 entry.js
中的 require("!style!css!./style.css")
修改为 require("./style.css")
,然后执行:
$ webpack entry.js bundle.js --module-bind 'css=style!css'
显然,这两种使用 loader 的方式,效果是一样的。最终的目录结构如下:
. ├── bundle.js ├── entry.js ├── index.html ├── module.js ├── node_modules ├── package.json ├── style.css
loader还可以接受参数,不同的参数可以让loader有不同的行为(前提是loader确实支持不同的行为),具体每个loader支持什么样的参数可以参考loader的文档。loader的使用有三种方法,分别是:
在require中显式指定,如:
在命令行中指定,如: $ webpack entry.js output.js --module-bind 'css=style!css'
在配置项(webpack.config.js)中指定,如:
第一种显式指定,即在 JS
文件中指定:
require('style!css!./style.css');`
第二种在命令行中指定参数的用法用得较少,可以这样写:
$ webpack --module-bind jade --module-bind 'css=style!css'
使用 --module-bind
指定loader,如果后缀和loader一样,直接写就好了,比如jade表示.jade文件用jade-loader处理,如果不一样,则需要显示指定,如 css=style!css
表示分别使用 css-loader
和 style-loader
处理 .css
文件。
第三种在配置项中指定是最灵活的方式,它的指定方式是这样:
module: { // loaders是一个数组,每个元素都用来指定loader loaders: [{ test: //.jade$/, //test值为正则表达式,当文件路径匹配时启用 loader: 'jade', //指定使用什么loader,可以用字符串,也可以用数组 exclude: /regexp/, //可以使用exclude来排除一部分文件 //可以使用query来指定参数,也可以在loader中用和require一样的用法指定参数,如`jade?p1=1` query: { p1:'1' } }, { test: //.css$/, loader: 'style!css' //loader可以和require用法一样串联 }, { test: //.css$/, loaders: ['style', 'css'] //也可以用数组指定loader }] }
Webpack 在执行的时候,除了在命令行传入参数,还可以通过指定的配置文件来执行。默认情况下,会搜索当前目录的 webpack.config.js 文件,这个文件是一个 node.js 模块,返回一个 json 格式的配置信息对象,或者通过 --config 选项来指定配置文件。
继续我们的案例,创建配置文件 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' } ] } }
同时简化 entry.js 中的 style.css 加载方式:
require('./style.css')
最后运行 webpack
,可以看到 webpack 通过配置文件执行的结果和上一节通过命令行 webpack entry.js bundle.js --module-bind 'css=style!css'
执行的结果是一样的。
插件可以完成更多 loader 不能完成的功能。
插件的使用一般是在 webpack 的配置信息 plugins 选项中指定。
Webpack 本身内置了一些常用的插件,还可以通过 npm 安装第三方插件。
接下来,我们利用一个最简单的 BannerPlugin 内置插件来实践插件的配置和运行,这个插件的作用是给输出的文件头部添加注释信息。
修改 webpack.config.js,添加 plugins:
var webpack = require('webpack') module.exports = { entry: './entry.js', output: { path: __dirname, filename: 'bundle.js' }, module: { loaders: [ {test: //.css$/, loader: 'style!css'} ] }, plugins: [ new webpack.BannerPlugin('This file is created by zhaoda') ] }
然后运行 webpack,打开 bundle.js,可以看到文件头部出现了我们指定的注释信息:
/*! This file is created by zhaoda */ /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; // 后面代码省略
entry参数定义了打包后的入口文件,可以是个字符串或数组或者是对象;如果是数组,数组中的所有文件会打包生成一个filename文件;如果是对象,可以将不同的文件构建成不同的文件:
{ entry: { page1: "./page1", //支持数组形式,将加载数组中的所有模块,但以最后一个模块作为输出 page2: ["./entry1", "./entry2"] }, output: { path: "dist/js/page", publicPath: "/output/", filename: "[name].bundle.js" } }
该段代码最终会生成一个 page1.bundle.js 和 page2.bundle.js,并存放到 ./dist/js/page 文件夹下
output参数是个对象,定义了输出文件的位置及名字:
output: { path: "dist/js/page", publicPath: "/output/", filename: "[name].bundle.js" }
path
: 打包文件存放的绝对路径
publicPath
: 网站运行时的访问路径
filename
:打包后的文件名
当我们在 entry
中定义构建多个文件时, filename
可以对应的更改为 [name].js
用于定义不同文件构建后的名字。
在webpack中JavaScript,CSS,LESS,TypeScript,JSX,CoffeeScript,图片等静态文件都是模块,不同模块的加载是通过模块加载器(webpack-loader)来统一管理的。loaders之间是可以串联的,一个加载器的输出可以作为下一个加载器的输入,最终返回到JavaScript上:
module: { //加载器配置 loaders: [ //.css 文件使用 style-loader 和 css-loader 来处理 { test: //.css$/, loader: 'style-loader!css-loader' }, //.js 文件使用 jsx-loader 来编译处理 { test: //.js$/, loader: 'jsx-loader?harmony' }, //.scss 文件使用 style-loader、css-loader 和 sass-loader 来编译处理 { test: //.scss$/, loader: 'style!css!sass?sourceMap'}, //图片文件使用 url-loader 来处理,小于8kb的直接转为base64 { test: //.(png|jpg)$/, loader: 'url-loader?limit=8192'} ] }
字段 | 说明 |
---|---|
test | 表示匹配的资源类型 |
loader 或 loaders | 表示用来加载这种类型的资源的loader |
! | 定义loader的串联关系,多个loader之间用“!”连接起来 |
此外,还可以添加用来定义png、jpg这样的图片资源在小于10k时自动处理为base64图片的加载器:
{ test: //.(png|jpg)$/,loader: 'url-loader?limit=10000'}
给css和less还有图片添加了loader之后,我们不仅可以像在node中那样 require()
js文件了,我们还可以 require()
css、less甚至图片文件:
require('./bootstrap.css'); require('./myapp.less'); var img = document.createElement('img'); img.src = require('./glyph.png');
注意, require()
还支持在资源path前面指定loader,即 require(![loaders list]![source path])
形式:
require("!style!css!less!bootstrap/less/bootstrap.less"); // “bootstrap.less”这个资源会先被"less-loader"处理, // 其结果又会被"css-loader"处理,接着是"style-loader" // 可类比pipe操作
require()
时指定的loader会覆盖配置文件里对应的loader配置项。
webpack在构建包的时候会按目录的进行文件的查找, resolve
属性中的 extensions
数组中用于配置程序可以自行补全哪些文件后缀:
resolve: { //查找module的话从这里开始查找 root: '/pomy/github/flux-example/src', //绝对路径 //自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名 extensions: ['', '.js', '.json', '.scss'], //模块别名定义,方便后续直接引用别名,无须多写长长的地址 alias: { AppStore : 'js/stores/AppStores.js',//后续直接 require('AppStore') 即可 ActionType : 'js/actions/ActionType.js', AppAction : 'js/actions/AppAction.js' } }
然后我们想要加载一个js文件时,只要 require('common')
就可以加载 common.js
文件了。
注意一下, extensions
第一个是空字符串! 对应不需要后缀的情况.
webpack提供了[丰富的组件]用来满足不同的需求,当然我们也可以自行实现一个组件来满足自己的需求:
plugins: [ //your plugins list ]
在webpack中编写js文件时,可以通过require的方式引入其他的静态资源,可通过loader对文件自动解析并打包文件。通常会将js文件打包合并,css文件会在页面的header中嵌入style的方式载入页面。但开发过程中我们并不想将样式打在脚本中,最好可以独立生成css文件,以外链的形式加载。这时 extract-text-webpack-plugin
插件可以帮我们达到想要的效果。需要使用npm的方式加载插件,然后参见下面的配置,就可以将js中的css文件提取,并以指定的文件名来进行加载。
npm install extract-text-webpack-plugin –-save-dev
plugins: [ new ExtractTextPlugin('styles.css') ]
当我们想在项目中require一些其他的类库或者API,而又不想让这些类库的源码被构建到运行时文件中,这在实际开发中很有必要。此时我们就可以通过配置externals参数来解决这个问题:
externals: { "jquery": "jQuery" }
这样我们就可以放心的在项目中使用这些API了: var jQuery = require(“jquery”)
;
当我们在require一个模块的时候,如果在require中包含变量,像这样:
require("./mods/" + name + ".js");
那么在编译的时候我们是不能知道具体的模块的。但这个时候,webpack也会为我们做些分析工作:
1.分析目录:’./mods’;2.提取正则表达式:’/^.*.js$/’;
于是这个时候为了更好地配合wenpack进行编译,我们可以给它指明路径,像在cake-webpack-config中所做的那样(我们在这里先忽略abcoption的作用):
var currentBase = process.cwd(); var context = abcOptions.options.context ? abcOptions.options.context : path.isAbsolute(entryDir) ? entryDir : path.join(currentBase, entryDir);
关于 webpack.config.js 更详尽的配置可以参考 Webpack Configuration 。
当项目逐渐变大,webpack 的编译时间会变长,可以通过参数让编译的输出内容带有进度和颜色。
$ webpack --progress --colors
如果不想每次修改模块后都重新编译,那么可以启动监听模式。开启监听模式后,没有变化的模块会在编译后缓存到内存中,而不会每次都被重新编译,所以监听模式的整体速度是很快的。
$ webpack --progress --colors --watch
当然,使用 webpack-dev-server
开发服务是一个更好的选择。它将在 localhost:8080 启动一个 express 静态资源 web 服务器,并且会以监听模式自动运行 webpack,在浏览器打开 http://localhost:8080/ 或 http://localhost:8080/webpack-dev-server/ 可以浏览项目中的页面和编译后的资源输出,并且通过一个 socket.io 服务实时监听它们的变化并自动刷新页面。
# 安装 $ npm install webpack-dev-server -g # 运行 $ webpack-dev-server --progress --colors
Webpack相关:
$ npm install webpack -g $ npm install webpack-dev-server -g # 安装必要的 loader: ## 编译 JSX $ npm install --save-dev babel-loader ## CSS 文件处理 $ npm install --save-dev css-loader style-loader ## React $ npm install --save-dev react-hot-loader
Babel 相关:
$ npm install --save-dev babel-core # 添加 ES6 支持 $ npm install --save-dev babel-preset-es2015 $ npm install --save-dev babel-react
var webpack = require('webpack'); module.exports = { entry: [ 'webpack/hot/only-dev-server', "./js/app.js" ], output: { path: './build', filename: "bundle.js" }, module: { loaders: [ { test: //.js?$/, loaders: ['react-hot', 'babel'], exclude: /node_modules/ }, { test: //.js$/, exclude: /node_modules/, loader: 'babel-loader'}, { test: //.css$/, loader: "style!css" } ] }, resolve:{ extensions:['','.js','.json'] }, plugins: [ new webpack.NoErrorsPlugin() ] };
参考资料:
React Webpack Cookbook
Babel 入门教程
Webpack 中文指南
Webpack 官方文档