webpack 学习
我的
webpack.config.js
module.exports = { entry: [ './src/js/app.js', './src/js/my.js' ], output: { path: __dirname + '/output/', publicPath: "/output/", filename: 'main.js' }, module: { loaders: [ {test: /\.(jpg|png)$/, loader: "url?limit=8192"}, { test: /\.css$/, loader: "style!css" } ] } };
package.json
{ "name": "webpack", "version": "0.0.1", "description": "webpack", "main": "index.js", "scripts": { "test": "npm start" }, "author": "libin", "license": "ISC" }
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script src="output/main.js"></script> </head> <body> <p></p> </body> </html>
app.js
document.write(require('./my.js'));
my.js
require('../css/app.css'); //会将此css文件打到页面里面 module.exports = "webpack现在的内容是来自于my.js文件!";
a.png
app.css
body{ background: green; text-align: center; font-size: 25px; border: 100px solid red; } p{ width: 100px; height: 100px; background:url(../img/a.png); }
最后生成的output 文件main.js
/******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ // __webpack_public_path__ /******/ __webpack_require__.p = "/output/"; /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { __webpack_require__(1); module.exports = __webpack_require__(2); /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { document.write(__webpack_require__(2)); /***/ }, /* 2 */ /***/ function(module, exports) { module.exports = "webpack现在的内容是来自于my.js文件!"; /***/ } /******/ ]);
执行:
webpack-dev-server --progress --colors
浏览器打开:
如今,越来越多的JavaScript代码被使用在页面上,我们添加很多的内容在浏览器里。如何去很好的组织这些代码,成为了一个必须要解决的难题。
对于模块的组织,通常有如下几种方法:
- 通过书写在不同文件中,使用script标签进行加载
- CommonJS进行加载(NodeJS就使用这种方式)
- AMD进行加载(require.js使用这种方式)
- ES6模块
思考:为什么只有JS需要被模块化管理,前台的很多预编译内容,不需要管理吗?
基于以上的思考,WebPack项目有如下几个目标:
- 将依赖树拆分,保证按需加载
- 保证初始加载的速度
- 所有静态资源可以被模块化
- 可以整合第三方的库和模块
- 可以构造大系统
从下图可以比较清晰的看出WebPack的功能
WebPack的特点
- 丰富的插件,方便进行开发工作
- 大量的加载器,包括加载各种静态资源
- 代码分割,提供按需加载的能力
- 发布工具
WebPack的优势
- webpack 是以 commonJS 的形式来书写脚本滴,但对 AMD/CMD 的支持也很全面,方便旧项目进行代码迁移。
- 能被模块化的不仅仅是 JS 了。
- 开发便捷,能替代部分 grunt/gulp 的工作,比如打包、压缩混淆、图片转base64等。
- 扩展性强,插件机制完善,特别是支持 React 热插拔(见 react-hot-loader )的功能让人眼前一亮。
WebPack的安装
- 安装命令
$ npm install webpack -g
- 使用webpack
$ npm init # 会自动生成一个package.json文件 $ npm install webpack --save-dev #将webpack增加到package.json文件中
- 可以使用不同的版本
$ npm install webpack@1.2.x --save-dev
- 如果想要安装开发工具
$ npm install webpack-dev-server --save-dev
WebPack的配置
每个项目下都必须配置有一个 webpack.config.js ,它的作用如同常规的 gulpfile.js/Gruntfile.js ,就是一个配置项,告诉 webpack 它需要做什么。
下面是一个例子
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
module.exports = {
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
index : './src/js/page/index.js'
},
//入口文件输出配置
output: {
path: 'dist/js/page',
filename: '[name].js'
},
module: {
//加载器配置
loaders: [
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.js$/, loader: 'jsx-loader?harmony' },
{ test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
]
},
//其它解决方案配置
resolve: {
root: 'E:/github/flux-example/src', //绝对路径
extensions: ['', '.js', '.json', '.scss'],
alias: {
AppStore : 'js/stores/AppStores.js',
ActionType : 'js/actions/ActionType.js',
AppAction : 'js/actions/AppAction.js'
}
}
};
- plugins 是插件项,这里我们使用了一个 CommonsChunkPlugin的插件,它用于提取多个入口文件的公共脚本部分,然后生成一个 common.js 来方便多页面之间进行复用。
- entry 是页面入口文件配置,output 是对应输出项配置 (即入口文件最终要生成什么名字的文件、存放到哪里)
- module.loaders 是最关键的一块配置。它告知 webpack 每一种文件都需要使用什么加载器来处理。 所有加载器需要使用npm来加载
- 最后是 resolve 配置,配置查找模块的路径和扩展名和别名(方便书写)
WebPack开始使用
这里有最基本的使用方法,给大家一个感性的认识
- 正确安装了WebPack,方法可以参考上面
- 书写entry.js文件
document.write("看看如何让它工作!");
- 书写index.html文件
<html> <head> <meta charset="utf-8"> </head> <body> <script type="text/javascript" src="bundle.js" charset="utf-8"></script> </body> </html>
- 执行命令,生成bundle.js文件
$ webpack ./entry.js bundle.js
- 在浏览器中打开index.html文件,可以正常显示出预期
- 增加一个content.js文件
module.exports = "现在的内容是来自于content.js文件!";
- 修改entry.js文件
document.write(require("./content.js"));
- 执行第四步的命令
进行加载器试验
- 增加style.css文件
body { background: yellow; }
- 修改entry.js文件
require("!style!css!./style.css"); document.write(require("./content.js"));
- 执行命令,安装加载器
$ npm install css-loader style-loader # 安装的时候不使用 -g
- 执行webpack命令,运行看效果
- 可以在命令行中使用loader
$ webpack ./entry.js bundle.js --module-bind "css=style!css"
使用配置文件
默认的配置文件为webpack.config.js
- 增加webpack.config.js文件
module.exports = { entry: "./entry.js", output: { path: __dirname, filename: "bundle.js" }, module: { loaders: [ { test: /\.css$/, loader: "style!css" } ] } };
- 执行程序
$ webpack
发布服务器
- 安装服务器
$ npm install webpack-dev-server -g $ webpack-dev-server --progress --colors
- 服务器可以自动生成和刷新,修改代码保存后自动更新画面
http://localhost:8080/webpack-dev-server/bundle
原文链接:http://www.jianshu.com/p/b95bbcfc590d
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
其优势主要可以归类为如下几个:
1. webpack 是以 commonJS 的形式来书写脚本滴,但对 AMD/CMD 的支持也很全面,方便旧项目进行代码迁移。
2. 能被模块化的不仅仅是 JS 了。
3. 开发便捷,能替代部分 grunt/gulp 的工作,比如打包、压缩混淆、图片转base64等。
4. 扩展性强,插件机制完善,特别是支持 React 热插拔(见 react-hot-loader )的功能让人眼前一亮。
我们谈谈第一点。以 AMD/CMD 模式来说,鉴于模块是异步加载的,所以我们常规需要使用 define 函数来帮我们搞回调:
define(['package/lib'], function(lib){function foo(){
lib.log('hello world!');
}
return {
foo: foo
};
});
另外为了可以兼容 commonJS 的写法,我们也可以将 define 这么写:
define(function (require, exports, module){var someModule = require("someModule");
var anotherModule = require("anotherModule");
someModule.doTehAwesome();
anotherModule.doMoarAwesome();
exports.asplode = function (){
someModule.doTehAwesome();
anotherModule.doMoarAwesome();
};
});
然而对 webpack 来说,我们可以直接在上面书写 commonJS 形式的语法,无须任何 define (毕竟最终模块都打包在一起,webpack 也会最终自动加上自己的加载器):
var someModule = require("someModule");var anotherModule = require("anotherModule");
someModule.doTehAwesome();
anotherModule.doMoarAwesome();
exports.asplode = function (){
someModule.doTehAwesome();
anotherModule.doMoarAwesome();
};
这样撸码自然更简单,跟回调神马的说 byebye~
不过即使你保留了之前 define 的写法也是可以滴,毕竟 webpack 的兼容性相当出色,方便你旧项目的模块直接迁移过来。
安装和配置 一. 安装
我们常规直接使用 npm 的形式来安装:
$ npm install webpack -g
当然如果常规项目还是把依赖写入 package.json 包去更人性化:
$ npm init$ npm install webpack --save-dev
二. 配置每个项目下都必须配置有一个 webpack.config.js ,它的作用如同常规的 gulpfile.js/Gruntfile.js ,就是一个配置项,告诉 webpack 它需要做什么。
我们看看下方的示例:
var webpack = require('webpack');var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
module.exports = {
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
index : './src/js/page/index.js'
},
//入口文件输出配置
output: {
path: 'dist/js/page',
filename: '[name].js'
},
module: {
//加载器配置
loaders: [
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.js$/, loader: 'jsx-loader?harmony' },
{ test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
]
},
//其它解决方案配置
resolve: {
root: 'E:/github/flux-example/src', //绝对路径
extensions: ['', '.js', '.json', '.scss'],
alias: {
AppStore : 'js/stores/AppStores.js',
ActionType : 'js/actions/ActionType.js',
AppAction : 'js/actions/AppAction.js'
}
}
};
⑴ plugins 是插件项,这里我们使用了一个 CommonsChunkPlugin的插件,它用于提取多个入口文件的公共脚本部分,然后生成一个 common.js 来方便多页面之间进行复用。
⑵ entry 是页面入口文件配置,output 是对应输出项配置 (即入口文件最终要生成什么名字的文件、存放到哪里) ,其语法大致为:
{entry: {
page1: "./page1",
//支持数组形式,将加载数组中的所有模块,但以最后一个模块作为输出
page2: ["./entry1", "./entry2"]
},
output: {
path: "dist/js/page",
filename: "[name].bundle.js"
}
}
该段代码最终会生成一个 page1.bundle.js 和 page2.bundle.js,并存放到 ./dist/js/page 文件夹下。
⑶ module.loaders 是最关键的一块配置。它告知 webpack 每一种文件都需要使用什么加载器来处理:
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'}
]
}
如上,"-loader"其实是可以省略不写的,多个loader之间用“!”连接起来。
注意所有的加载器都需要通过 npm 来加载,并建议查阅它们对应的 readme 来看看如何使用。
拿最后一个 url-loader 来说,它会将样式中引用到的图片转为模块来处理,使用该加载器需要先进行安装:
npm install url-loader -save-dev
配置信息的参数“?limit=8192”表示将所有小于8kb的图片都转为base64形式 (其实应该说超过8kb的才使用 url-loader 来映射到文件,否则转为data url形式) 。
你可以 点这里 查阅全部的 loader 列表。
⑷ 最后是 resolve 配置,这块很好理解,直接写注释了:
resolve: {//查找module的话从这里开始查找
root: 'E:/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'
}
}
关于 webpack.config.js 更详尽的配置可以参考 这里 。
运行 webpack
webpack 的执行也很简单,直接执行
$ webpack --display-error-details
即可,后面的参数“--display-error-details”是推荐加上的,方便出错时能查阅更详尽的信息(比如 webpack 寻找模块的过程),从而更好定位到问题。
其他主要的参数有:
$ webpack --config XXX.js //使用另一份配置文件(比如webpack.config2.js)来打包$ webpack --watch //监听变动并自动打包
$ webpack -p //压缩混淆脚本,这个非常非常重要!
$ webpack -d //生成map映射文件,告知哪些模块被最终打包到哪里了
其中的 -p 是很重要的参数,曾经一个未压缩的 700kb 的文件,压缩后直接降到 180kb (主要是样式这块一句就独占一行脚本,导致未压缩脚本变得很大) 。
模块引入
上面唠嗑了那么多配置和执行方法,下面开始说说寻常页面和脚本怎么使用呗。
一. HTML直接在页面引入 webpack 最终生成的页面脚本即可,不用再写什么 data-main 或seajs.use 了:
<!DOCTYPE html><html>
<head lang="en">
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<script src="dist/js/page/common.js"></script>
<script src="dist/js/page/index.js"></script>
</body>
</html>
可以看到我们连样式都不用引入,毕竟脚本执行时会动态生成<style>并标签打到head里。
二. JS各脚本模块可以直接使用 commonJS 来书写,并可以直接引入未经编译的模块,比如 JSX、sass、coffee等(只要你在 webpack.config.js 里配置好了对应的加载器)。
我们再看看编译前的页面入口文件(index.js):
require('../../css/reset.scss'); //加载初始化样式require('../../css/allComponent.scss'); //加载组件样式
var React = require('react');
var AppWrap = require('../component/AppWrap'); //加载组件
var createRedux = require('redux').createRedux;
var Provider = require('redux/react').Provider;
var stores = require('AppStore');
var redux = createRedux(stores);
var App = React.createClass({
render: function() {
return (
<Provider redux={redux}>
{function() { return <AppWrap />; }}
</Provider>
);
}
});
React.render(
<App />, document.body
);
一切就是这么简单么么哒~ 后续各种有的没的,webpack 都会帮你进行处理。
其他
至此我们已经基本上手了 webpack 的使用,下面是补充一些有用的技巧。
一. shimming在 AMD/CMD 中,我们需要对不符合规范的模块(比如一些直接返回全局变量的插件)进行 shim 处理,这时候我们需要使用 exports-loader 来帮忙:
{ test: require.resolve("./src/js/tool/swipe.js"), loader: "exports?swipe"}之后在脚本中需要引用该模块的时候,这么简单地来使用就可以了:
require('./tool/swipe.js');swipe();
二. 自定义公共模块提取
在文章开始我们使用了CommonsChunkPlugin 插件来提取多个页面之间的公共模块,并将该模块打包为 common.js 。
但有时候我们希望能更加个性化一些,我们可以这样配置:
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");module.exports = {
entry: {
p1: "./page1",
p2: "./page2",
p3: "./page3",
ap1: "./admin/page1",
ap2: "./admin/page2"
},
output: {
filename: "[name].js"
},
plugins: [
new CommonsChunkPlugin("admin-commons.js", ["ap1", "ap2"]),
new CommonsChunkPlugin("commons.js", ["p1", "p2", "admin-commons.js"])
]
};
// <script>s required:
// page1.html: commons.js, p1.js
// page2.html: commons.js, p2.js
// page3.html: p3.js
// admin-page1.html: commons.js, admin-commons.js, ap1.js
// admin-page2.html: commons.js, admin-commons.js, ap2.js
三. 独立打包样式文件
有时候可能希望项目的样式能不要被打包到脚本中,而是独立出来作为.css,然后在页面中以<link>标签引入。这时候我们需要 extract-text-webpack-plugin 来帮忙:
var webpack = require('webpack');var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
plugins: [commonsPlugin, new ExtractTextPlugin("[name].css")],
entry: {
//...省略其它配置
最终 webpack 执行后会乖乖地把样式文件提取出来:
四. 与 grunt/gulp 配合
以 gulp 为示例,我们可以这样混搭:
gulp.task("webpack", function(callback) {// run webpack
webpack({
// configuration
}, function(err, stats) {
if(err) throw new gutil.PluginError("webpack", err);
gutil.log("[webpack]", stats.toString({
// output options
}));
callback();
});
});
当然我们只需要把配置写到 webpack({ ... }) 中去即可,无须再写 webpack.config.js 了。
更多参照信息请参阅: grunt配置 / gulp配置 。
五. React 相关
⑴ 推荐使用 npm install react 的形式来安装并引用 React 模块,而不是直接使用编译后的 react.js,这样最终编译出来的 React 部分的脚本会减少 10-20 kb左右的大小。
⑵ react-hot-loader 是一款非常好用的 React 热插拔的加载插件,通过它可以实现修改-运行同步的效果,配合 webpack-dev-server 使用更佳!
基于 webpack 的入门指引就到这里,希望本文能对你有所帮助,你也可以参考下述的文章来入门:
webpack入门指谜
webpack-howto
共勉~
最近在看许多React的资料,发现了大部分的项目都是用webpack行模块化管理的工具。这次也是借着写了一个React-Todos的小应用,对webPack最基本实用的功能体验了一番,顺带做个小记录。
#为什么用webpack
CommonJs与AMD
在一开始,我们先讲一下它和以往我们所用的模块管理工具有什么不一样。在最开始的阶段,Js并没有这些模块机制,各种Js到处飞,得不到有效妥善的管理。后来前端圈开始制定规范,最耳熟能详的是CommonJs和AMD。
CommonJs是应用在NodeJs,是一种同步的模块机制。它的写法大致如下:
var firstModule = require("firstModule");
//your code...
module.export = anotherModule
AMD的应用场景则是浏览器,异步加载的模块机制。require.js的写法大致如下:
define(['firstModule'], function(module){
//your code...
return anotherModule
})
其实我们单比较写法,就知道CommonJs是更为优秀的。它是一种同步的写法,对Human友好,而且代码也不会繁琐臃肿。但更重要的原因是, 随着npm成为主流的JavaScript组件发布平台,越来越多的前端项目也依赖于npm上的项目,或者自身就会发布到npm平台。 所以我们对如何可以使用npm包中的模块是我们的一大需求。所以browserify工具就出现了,它支持我们直接使用 require()
的同步语法去加载npm模块。
当然我们这里不得不说的是,ES2015(ES6)里也有了自己的模块机制,也就是说ES6的模块机制是官方规定的,我们通过 babel (一种6to5的编译器)可以使用比较多的新特性了,包括我们提到的模块机制,而它的写法大致如下:
import {someModule} from "someModule";
// your codes...
export anotherModule;
当然上面的写法只是最基本的,还有其他的不同加载模块的写法,可以看一下阮一峰老师的 ECMAScript 6 入门 或者babel的相关文档 Learn ES2015 。
功能特性
browserify的出现非常棒,但webpack更胜一筹!
我们来看看webpack支持哪些功能特性:
- 支持CommonJs和AMD模块,意思也就是我们基本可以无痛迁移旧项目。
- 支持模块加载器和插件机制,可对模块灵活定制。特别是我最爱的babel-loader,有效支持ES6。
- 可以通过配置,打包成多个文件。有效利用浏览器的缓存功能提升性能。
- 将样式文件和图片等静态资源也可视为模块进行打包。配合loader加载器,可以支持sass,less等CSS预处理器。
- 内置有source map,即使打包在一起依旧方便调试。
看完上面这些,可以想象它就是一个前端工具,可以让我们进行各种模块加载,预处理后,再打包。之前我们对这些的处理是放在grunt或gulp等前端自动化工具中。有了webpack,我们无需借助自动化工具对模块进行各种处理,让我们工具的任务分的更加清晰。
我们看一下官方对webpack理解的图。
任何静态资源都可以视作模块,然后模块之间也可以相互依赖,通过webpack对模块进行处理后,可以打包成我们想要的静态资源。
既然已经大致知道为什么我们要使用webpack了,我们接下来就开始使用webpack吧!
#开始使用webpack
首先新建一个webpack101的项目,我们将在webpack101这里开展我们接下来的各项学习。
$ npm init // 用于初始化项目的package.json
//初始化文件目录:
webpack101
--- src
--- entry.js
--- module1.js
--- index.html
--- package.json
--- webpack.config.js
安装webpack
我们通过npm来将webpack安装到全局
$ npm install webpack -g
一个最简单的webpack
webpack配置
webpack是需要进行配置的,我们在使用webpack的时候,会默认 webpack.config.js
为我们的配置文件。所以接下来,我们新建这个js文件。
// webpack.config.js
var path = require("path");
module.exports = {
entry: '../src/entry.js', //演示单入口文件
output: {
path: path.join(__dirname, 'out'), //打包输出的路径
filename: 'bundle.js', //打包后的名字
publicPath: "./out/" //html引用路径,在这里是本地地址。
}
};
编写入口文件
接下来就编写我们的入口文件 entry.js
和第一个模块文件 module1.js
。我们一切从简,里面只用来加载一个Js模块。
// entry.js
require("./module1"); // 使用CommonJs来加载模块
下一个文件
// module1.js
console.log("Hello Webpack!");
启动webpack
一切准备好后,我们仅需要在项目根目录下,用命令行 webpack
执行一下即可。
// webpack 命令行的几种基本命令
$ webpack // 最基本的启动webpack方法
$ webpack -w // 提供watch方法,实时进行打包更新
$ webpack -p // 对打包后的文件进行压缩,提供production
$ webpack -d // 提供source map,方便调试。
webpack成功运行后,我们就可以看到根目录出现了out文件夹,里面有我们打包生成的 bundle.js
。我们最后通过在 index.html
里对这个文件引入就可以了。我们可以在控制台看到我们想要的结果, Hello Webpack !
多模块依赖
刚才的例子,我们仅仅是跑通了webpack通过 entry.js
入口文件进行打包的例子。下面我们就来看一下它是否真的支持CommonJs和AMD两种模块机制呢?下面我们新建多几个js文件吧!
// 修改module1.js
require(["./module3"], function(){
console.log("Hello Webpack!");
});
下一个文件
// module2.js,使用的是CommonJs机制导出包
module.exports = function(a, b){
return a + b;
}
下一个文件
// module3.js,使用AMD模块机制
define(['./module2.js'], function(sum){
return console.log("1 + 2 = " + sum(1, 2));
})
其实像上面这样混用两种不同机制非常不好,这里仅仅是展示用的,在开发新项目时还是推荐CommonJs或ES2015的Module。当然我个人更倾向于ES2015的模块机制的~
loader加载器
到了我最喜欢也是最激动人心的功能了!我们先想想应用场景,前端社区有许多预处理器供我们使用。我们可以使用这些预处理器做一些强大的事情,大家都听过的就是 CoffeeScript
和 Sass
了。我们以前要编译这些预处理器,就是用 gulp
进行编译。但是我们对这些文件处理其实也挺繁琐的,webpack可以一次性解决!
在这里我们用Sass和babel编译ES2015为例子,看一下loader是如何使用的。
安装loader
我们第一步就是先要安装好各个必须的loader,我们直接看看需要通过npm安装什么。
$ npm install style-loader css-loader url-loader babel-loader sass-loader file-loader --save-dev
配置loader
安装完各个loader后,我们就需要配置一下我们的 webpack.config.js
,载入我们的loader。
// webpack.config.js
module.exports = {
entry: path.join(__dirname, 'src/entry.js'),
output: {
path: path.join(__dirname, 'out'),
publicPath: "./out/",
filename: 'bundle.js'
},
// 新添加的module属性
module: {
loaders: [
{test: /\.js$/, loader: "babel"},
{test: /\.css$/, loader: "style!css"},
{test: /\.(jpg|png)$/, loader: "url?limit=8192"},
{test: /\.scss$/, loader: "style!css!sass"}
]
}
};
我们主要看看module的loaders。loaders是一个数组,里面的每一个对象都用正则表达式,对应着一种配对方案。比如匹配到js后缀名就用babel-loader,匹配到scss后缀名的就先用sass,再用css,最后用style处理,不同的处理器通过 !
分隔并串联起来。这里的loader是可以省略掉 -loader
这样的,也就是原本应该写成 style-loader!css-loader!sass-loader
,当然我们必须惜字如金,所以都去掉后面的东东。
我们仅仅是配置一下,已经是可以直接用ES2015和SASS去写我们的前端代码了。在此之前,我们对src文件夹里再细分成js,css,image三个文件夹,处理好分层。话不多说,赶紧试试。
稍微复杂的webpack项目
bebel-loader
// js/es6-module.js
class People{
constructor(name){
this.name = name;
}
sayhi(){
console.log(`hi ${this.name} !`);
}
}
exports.module = People;
写好模块后,我们直接在 entry.js
入口文件中引入该模块。
// entry.js
// javascript
require('./js/module1');
let People = require('./js/es6-module');
let p = new People("Yika");
p.sayHi();
// css
require('./css/main.scss');
哈哈哈,不能再爽!这下子我们可以使用很多优秀的ES6特性去构建大型的web了。
sass-loader
大家或许注意到了下方的css的require,那就是用来加载Sass样式的。我们通过启动style-loader会将css代码转化到 <style>
标签内,我们看一下里面的内容。
// css/main.scss
html, body{
background: #dfdfdf;
}
最后我们打开 index.html
观察我们所有的结果,首先背景已经是淡灰色的,并且控制台也有我们想要的内容。我们通过查看DOM结构,可以发现 head
标签里多出了 style
标签,里面正是我们想要定制的样式。
关于对图片的打包
我们之前也说,webpack对与静态资源来说,也是看作模块来加载的。CSS我们是已经看过了,那图片是怎么作为模块打包加载进来呢?这里我们可以想到,图片我们是用url-loader加载的。我们在css文件里的url属性,其实就是一种封装处理过require操作。当然我们还有一种方式就是直接对元素的src属性进行require赋值。
div.img{
background: url(../image/xxx.jpg)
}
//或者
var img = document.createElement("img");
img.src = require("../image/xxx.jpg");
document.body.appendChild(img);
上述两种方法都会对符合要求的图片进行处理。而要求就是在url-loader后面通过query参数的方式实现的,这里就是说只有不大于8kb的图片才会打包处理成Base64的图片。关于query,请看文档: Query parameters
{test: /\.(jpg|png)$/, loader: "url?limit=8192"}
打包成多个资源文件
我们在开发多页面的站点的时候,还是需要希望能有多个资源文件的。这样我们就可以有效利用缓存提升性能,做到文件按需加载。如何写入口文件,这里就不再赘述了,我们直接看如何对 webpack.config.js
进行修改。
// webpack.config.js
entry: {
page1: "entry.js",
page2: "entry2.js"
},
output: {
path: path.join(__dirname, 'out'),
publicPath: "./out/",
filename: '[name].js'
}
这里重点关注两个地方,entry属性可以是一个对象,而对象名也就是key会作为下面output的filename属性的 [name]
。当然entry也可以是一个数组,更多用法都可以去webpack的 官方文档 进行查看。
当然webpack也考虑到公共模块的利用,我们利用插件就可以智能提取公共部分,以提供我们浏览器的缓存复用。我们只需要在 webpack.config.js
添加下面的代码即可。
// 修改添加,webpack.config.js
var webpack = require('webpack');
module.exports = {
// ....省略各种代码
plugins: [
new webpack.optimize.CommonsChunkPlugin('common.js')
]
}
我们做个小测试,让第二个入口文件也加载我们之前的 es6-module.js
。然后我们用webpack进行打包,就发现生成的 common.js
里是有相应代码的。我们需要手动在html上去加载 common.js
,并且是 必须要最先加载 。
独立出css样式
如果我们希望样式通过 <link>
引入,而不是放在 <style>
标签内呢,即使这样做会多一个请求。这个时候我们就要配合插件一起使用啦,我们一起来看看。
$ npm install extract-text-webpack-plugin --save-dev
安装完插件就要配置 webpack.config.js
了。我们添加以下代码
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
// ...省略各种代码
module: {
loaders: [
{test: /\.js$/, loader: "babel"},
{test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader")},
{test: /\.(jpg|png|svg)$/, loader: "url?limit=8192"},
{test: /\.scss$/, loader: "style!css!sass"}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('common.js'),
new ExtractTextPlugin("[name].css")
]
}
为了区分开用 <link>
链接和用 <style>
,我们这里以CSS后缀结尾的模块用插件。我们重点关注一下使用了ExtractTextPlugin的模块,在ExtractTextPlugin的extract方法有两个参数,第一个参数是经过编译后通过style-loader单独提取出文件来,而第二个参数就是用来编译代码的loader。
当然,插件也支持所有独立样式打包成一个css文件。增加多一个参数即可。
new ExtractTextPlugin("style.css", {allChunks: true})
至于怎样加载样式是最佳实践,这个就要自己平时多思考了。多站点多样式的时候,是做到一次性打包加载呢,还是按需加载呢?我这里就建议一项,主页尽量做到最精简,毕竟决定用户存留时间。
#总结
前端社区不断发展,越来越趋向于组件化的发展。通过webpack,我们就能体验到one component one module
的开发感觉。当然如何更好的使用webpack还是要通过不断的思考总结,才能找到最优的方案。