require
模塊依賴,一招搞定
require("./lib.js");
require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");
在 Webpack 當中, 所有的資源都被當作是模塊,。
加載器
對應各種不同文件類型的資源,,Webpack有對應的模塊loader
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的直接轉(zhuǎn)為base64
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
]
}
webpack的優(yōu)勢
- webpack 是以 commonJS 的形式來書寫腳本滴,,但對 AMD/CMD 的支持也很全面,,方便舊項目進行代碼遷移
- 所有靜態(tài)資源都可以被當成模塊引用,而不僅僅是JS了
- 開發(fā)便捷,,能替代部分 grunt/gulp 的工作,,比如打包、壓縮混淆,、圖片轉(zhuǎn)base64等
- 擴展性強,,插件機制完善,特別是支持 React 熱插拔(見 react-hot-loader )的功能讓人眼前一亮
以 AMD/CMD 模式來說,,鑒于模塊是異步加載的,,所以我們常規(guī)需要使用 define 函數(shù)來幫我們搞回調(diào):
define(['package/lib'], function(lib){
function foo(){
lib.log('hello world!');
}
return {
foo: foo
};
});
另外為了可以兼容 commonJS 的寫法,我們也可以將 define 這么寫:
define(function (require, exports, module){
var module1 = require("module1");
var module2 = require("module2");
module1.sayHello();
module2.sayHi();
exports.helloWorld = function (){
module1.sayHello();
module2.sayHi();
};
});
然而對 webpack 來說,,我們可以直接在上面書寫 commonJS 形式的語法,,無須任何 define (畢竟最終模塊都打包在一起,webpack 也會最終自動加上自己的加載器):
var module1 = require("module1");
var module2 = require("module2");
module1.sayHello();
module2.sayHi();
exports.helloWorld = function (){
module1.sayHello();
module2.sayHi();
};
不過即使你保留了之前 define 的寫法也是可以滴,,畢竟 webpack 的兼容性相當出色,,方便你舊項目的模塊直接遷移過來。
安裝使用
安裝webpack
首先確保機子上已安裝node.js,,然后通過npm安裝webpack
npm install webpack -g
啟動命令
切換到有 webpack.config.js 的目錄然后運行
webpack // 執(zhí)行一次開發(fā)的編譯
webpack -p // 針對發(fā)布環(huán)境編譯(壓縮代碼)
webpack -w // 進行開發(fā)過程持續(xù)的增量編譯(飛快地!)
webpack -d // 生成map映射文件,,告知哪些模塊被最終打包到哪里了
webpack --config XXX.js //使用另一份配置文件(比如webpack.config2.js)來打包
插件的安裝
所有的加載器都需要通過 npm 來加載,并建議查閱它們對應的 readme 來看看如何使用
npm install url-loader --save-dev
如果目錄沒有package.json,,則需要先init一下,,再運行npm install 命令
npm init
npm install url-loader --save-dev
配置文件(webpack.config.js)
每個項目下都必須配置有一個 webpack.config.js
- plugins 插件項
- entry 頁面入口文件配置
- output 對應輸出項配置(即入口文件最終要生成什么名字的文件、存放到哪里)
- module.loaders 最關鍵的一塊,,配置每一種文件需要使用什么加載器來處理(多個loader之間用"!"連接)
通用配置文件例子
// webpack.config.js
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin(/* chunkName= */'common', /* filename= */'common.js'); // 分析以下模塊的共用代碼, 單獨打一個包到common.js
var ExtractTextPlugin = require("extract-text-webpack-plugin"); // 單獨打包CSS
var HtmlWebpackPlugin = require('html-webpack-plugin'); // Html文件處理
module.exports = {
entry: {
Detail: './modules/app/detail.js',
Home: './modules/app/home.js'
},
output: {
path: './build', // This is where images & js will go
//publicPath: 'http://m./ppaweb/test/build/', // This is used to generate URLs to e.g. images
publicPath: '/ppaweb/example/build/', // This is used to generate URLs to e.g. images
filename: '[name].js',
chunkFilename: "[id].chunk.js?[hash:8]"
},
plugins: [
commonsPlugin,
new ExtractTextPlugin('[name].css', {allChunks: true}), // 單獨打包CSS
// 全局變量
new webpack.DefinePlugin({
//__DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV||'false')) //通過環(huán)境變量設置
__DEV__: 'false' // 開發(fā)調(diào)試時把它改為true
}),
/**
* HTML文件編譯,,自動引用JS/CSS
*
* filename - 輸出文件名,相對路徑output.path
* template - HTML模板,,相對配置文件目錄
* chunks - 只包含指定的文件(打包后輸出的JS/CSS),不指定的話,,它會包含生成的所有js和css文件
* excludeChunks - 排除指定的文件(打包后輸出的JS/CSS),比如:excludeChunks: ['dev-helper']
* hash
*/
new HtmlWebpackPlugin({filename: 'views/home.html', template: 'views/home.html', chunks: ['common', 'Home'], hash: true}),
new HtmlWebpackPlugin({filename: 'views/detail.html', template: 'views/detail.html', chunks: ['common', 'Detail'], hash: true})
],
module: {
loaders: [
{
test: /\.js$/, loader: 'babel-loader', // ES6
exclude: /(node_modules|bower_components|ppaweb\\libs\\webpack)/
},
// CSS,LESS打包進JS
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, // use ! to chain loaders
// CSS,LESS單獨打包
//{ test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") },
//{ test: /\.less$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader') },
{ test: /\.tpl$/, loader: 'ejs'}, // artTemplate/ejs 's tpl
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader',
query: {
name: '[path][name].[ext]?[hash:8]',
limit: 8192 // inline base64 URLs for <=8k images, direct URLs for the rest
}
}
]
},
resolve: {
alias: {
'lib0': '../../../ppaweb/libs/webpack', // 從module調(diào)用webpack上的公共lib庫路徑簡寫
'lib1': '../../../../ppaweb/libs/webpack', // 從module的子文件夾調(diào)用webpack上的公共lib庫路徑簡寫
'lib2': '../../../../../ppaweb/libs/webpack' // 從module的兩層子文件夾調(diào)用webpack上的公共lib庫路徑簡寫
},
// 現(xiàn)在可以寫 require('file') 代替 require('file.coffee')
extensions: ['', '.js', '.json', '.coffee']
}
};
具體可以參考:webpack-demo的配置項
Webpack常用功能
JS里:CSS及圖片引用
require('./bootstrap.css');
require('./myapp.less');
var img = document.createElement('img');
img.src = require('./glyph.png');
- Synchronous
- CSS和LESS會被打包到JS
- 圖片可能被轉(zhuǎn)化成 base64 格式的 dataUrl
module: {
loaders: [
//圖片文件使用 url-loader 來處理,,小于8kb的直接轉(zhuǎn)為base64
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
]
}
LESS/CSS里:圖片引用
background-image: url("./logo.png");
根據(jù)配置“url-loader?limit=xxx”來決定把圖片轉(zhuǎn)換成base64還是圖片鏈接形式引用,。
module: {
loaders: [
//圖片文件使用 url-loader 來處理,小于8kb的直接轉(zhuǎn)為base64
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
]
}
LESS/CSS里:@ import 路徑問題
LESS里可以通過@import mixin.less 進行模塊化開發(fā),,可以在import的路徑前面加上~,,表示路徑以模塊處理,支持alias,。
tnpm i @ali/pp-libs --save-dev
# index.less
@import '@ali/pp-libs/libs/base/reset.less';
CSS能單獨打包
有時候可能希望項目的樣式能不要被打包到腳本中,,而是獨立出來作為.css,然后在頁面中以標簽引入。這時候我們需要 extract-text-webpack-plugin 來幫忙,。
只需兩步:
- 插件安裝
npm install extract-text-webpack-plugin --save-dev
- 配置文件webpack.config.js
var ExtractTextPlugin = require("extract-text-webpack-plugin");
……
plugins: [
// 目標文件名規(guī)則[name].css
new ExtractTextPlugin('[name].css', {allChunks: true})
],
module: {
loaders: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") },
{ test: /\.less$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader') },
]
},
公共代碼自動抽離
提取多個頁面之間的公共模塊,,并將該模塊打包為 common.js
A.js, B.js => a.js, b.js, common.js
// 分析以下模塊的共用代碼, 單獨打一個包到common.js
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin(/*chunkName=*/'common', /*filename=*/'common.js');
plugins: [
commonsPlugin
],
記得要在HTML手動引入common.js
自定義公共模塊提取
上面是自動在所有入口的js中提取公共代碼,并打包為common.js,。
有時候我們希望能更加個性化一些,,比如我希望:
A.js+C.js => AC-common.js
B.js+D.js => BD-common.js
我們可以這樣配:
module.exports = {
entry: {
A: "./a.js",
B: "./b.js",
C: "./c.js",
D: "./d.js",
E: "./e.js"
},
output: {
filename: "[name].js"
},
plugins: [
new CommonsChunkPlugin("AC-commons.js", ["A", "C"]),
new CommonsChunkPlugin("BD-commons.js", ["B", "D"])
]
};
// <script>s required:
// a.html: AC-commons.js, A.js
// b.html: BD-commons.js, B.js
// c.html: AC-commons.js, C.js
// d.html: BD-commons.js, D.js
// e.html: E.js
HTML自動引用 JS/CSS
有時候我們連HTML里的JS/CSS資源都懶的寫,也是可行的,,HTML也可以當成模塊來寫,。
npm install html-webpack-plugin --save-dev
var HtmlWebpackPlugin = require('html-webpack-plugin'); // Html文件處理
module.exports = {
……
plugins: [
/**
* HTML文件編譯,,自動引用JS/CSS
*
* filename - 輸出文件名,,相對路徑output.path
* template - HTML模板,相對配置文件目錄
* chunks - 只包含指定的文件(打包后輸出的JS/CSS),不指定的話,,它會包含生成的所有js和css文件
* excludeChunks - 排除指定的文件(打包后輸出的JS/CSS),,比如:excludeChunks: ['dev-helper']
* hash
*/
new HtmlWebpackPlugin({filename: 'views/list.html', template: 'src/modules/app/list/index.html', chunks: ['common', 'List'], hash: true}),
new HtmlWebpackPlugin({filename: 'views/detail.html', template: 'src/modules/app/detail/index.html', chunks: ['common', 'Detail'], hash: true})
],
};
具體參考 webpack-demo的配置項
全局變量
有些代碼我們只想在開發(fā)環(huán)境使用(比如log),這里,,我們需要用到全局變量插件:webpack.DefinePlugin
module.exports = {
plugins: [
// 全局變量
new webpack.DefinePlugin({
// __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false')), //通過環(huán)境變量設置
__DEV__: JSON.stringify(JSON.parse('true')), // 開發(fā)調(diào)試時把它改為true
__HELLO__: JSON.stringify('hello world')
})
]
};
js中調(diào)用
if(__DEV__) {
console.log(__HELLO__);
}
注意:webpack -p 會執(zhí)行 uglify dead-code elimination, 任何這種代碼都會被剔除, 所以你不用擔心秘密功能泄漏.
異步加載
require.ensure
語法:
require.ensure(dependencies: String[],
callback: function([require]),
[chunkName: String])
與require AMD類似,,也是在需要的時候才會加載相應的模塊。但不同的是,,require.ensure在模塊被下載下來后(模塊還沒被執(zhí)行)便立即執(zhí)行回調(diào)函數(shù).
另外require.ensure可以指定構(gòu)建后chunk名,,如果之前已有require.ensure指定了該名稱,webpack會將這些模塊統(tǒng)一合并到一個模塊集里,。
簡單例子
// 異步加載
if(i < 0) {
require.ensure([], function() {
require('a.js');
});
}
定義異步加載文件名字(webpack.config.js)
output: {
chunkFilename: "[id].chunk.[hash:8].js"
},
生成的異步文件引用邏輯自動包含在源目標JS中,,不用手動引用,所以以上文件名隨便怎么定義都不影響,。
file-loader
圖片加載器url-loader其實是對file-loader的一個封裝
loaders: [
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader',
query: {
name: '[path][name].[ext]?[hash:8]',
limit: 8192
}
}
]
如果文件超出體積, 就給一個這樣規(guī)則的文件名
參考:https://github.com/webpack/file-loader
ES6支持
module: {
loaders: [
{
test: /\.js$/, loader: 'babel-loader', // ES6
exclude: /(node_modules|bower_components|ppaweb\\libs\\webpack)/
},
]
},
參考:http://npm./package/babel-loader
Alias:項目遷移更方便
webpack允許配置路徑的別名,,這樣在一些外部資源的依賴的時候顯得格外有用,對以后的項目遷移等都起到不小的作用,。
resolve: {
alias: {
// 從module調(diào)用公共libs上的庫路徑簡寫
'lib0': '../../../libs',
// 從module的子文件夾調(diào)用公共libs上的庫路徑簡寫
'lib1': '../../../../libs',
// 從module的兩層子文件夾調(diào)用公共libs上的庫路徑簡寫
'lib2': '../../../../../libs'
}
}
# module/index.js
require('lib0/proxy');
# module/app/index.js
require('lib1/proxy');
# module/app/header/index.js
require('lib2/proxy');
shimming
在 AMD/CMD 中,,我們需要對不符合規(guī)范的模塊(比如一些直接返回全局變量的插件)進行 shim 處理,這時候我們需要使用 exports-loader 來幫忙:
{ test: require.resolve("./src/js/tool/swipe.js"), loader: "exports?swipe"}
之后在腳本中需要引用該模塊的時候,,這么簡單地來使用就可以了:
require('./tool/swipe.js');
swipe();
externals
externals使用場景是外部依賴不需要打包進bundle
比如:你在頁面里通過script標簽引用了zepto:<script src="http://cdnjs./cdnjs/libs/zepto/1.1.4/zepto.min.js"></script> ,,所以并不想在其他js里再打包進入一遍
// webpack.config.js
...
{
externals: {
"zepto": "Zepto" // 引用時直接 var x = require('zepto');
}
}
// index.js
var $ = require('zepto');
編譯后會這樣
Webpack模塊編寫
模塊框架
// var $ = require('zepto');
// require('./index.less');
!(function () {
var module1 = (function () {
var _e = {};
_e.test = function () {
// do something here
};
return _e;
})();
window.module1 = module1;
try {
module.exports = module1;
} catch (e) {}
})();
模塊/組件打包
模塊/組件一般會發(fā)布到NPM或者其他地方提供給他人使用的,這里可以使用libraryTarget字段來控制webpack打包后輸出為模塊/組件,。
// webpack.config.js
module.exports = {
entry: {
pca: './src/main.js'
},
output: {
path: './dist',
filename: '[name].js',
libraryTarget: "umd" // 組件采用UMD格式打包
},
module: {
loaders: [
{
test: /\.js$/, loader: 'babel-loader',
exclude: /(node_modules|libs)/
}
]
}
};
這樣,,打包后發(fā)布到npm,別人就可以直接 npm install xxx 來安裝后,,可以 var a = require('xxx'); 來使用了,。
舊項目遷移方案
1. 入口文件
一般一個頁面(HTML)對應一個入口文件
/views/a.html
/views/b.html
/views/c.html
entry: {
A: 'modules/app/a.js',
B: 'modules/app/b.js',
C: 'modules/app/c.js'
}
2. 文件引用
3. 優(yōu)化
- common.js
- css單獨打包
- 異步加載
- HTML模板(html-webpack-plugin)
附錄
Q&A
Q. HTML里引用JS能自動生成訪問后綴嗎?比如a.js?2016
A. 插件html-webpack-plugin
|