前不久把 sf 前端的構建工具進行了改進和優化,用上了目前很是火的 webpack 、babel 和 es6 等等新技術。javascript
sf 前端的構建工具最先使用的是當時很是流行的 grunt,接下來是 gulp,而後就是如今的 webpack。css
構建工具 | Browserify | Grunt | Gulp | Webpack |
---|---|---|---|---|
描述 | browser-side require() the node way | The JavaScript Task Runner | The streaming build system | Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jade, coffee, css, less, ... and your custom stuff. |
關鍵詞 | browser, require, commonjs, commonj-esque, bundle, npm, javascript | task, async, cli, minify, uglify, build, lodash, unit, test, qunit, nodeunit, server, init, scaffold, make, jake, tool | ||
做者 | James Halliday | Grunt Development Team | Fractal | Tobias Koppers @sokra |
連接 | Homepage Bug Report Github | Homepage Bug Report Github | Homepage Bug Report Github | Homepage Bug Report Github |
比較 | ||||
Licenses | MIT | MIT | MIT | MIT |
Created | 5 years ago (Feb, 2011) | 5 years ago (Jan, 2012) | 3 years ago (Jul, 2013) | 4 years ago (Mar, 2012) |
版本數量 | 459 | 56 | 63 | 416 |
版本週期 | every 4 days | every a month | every 18 days | every 4 days |
依賴數 | 46 | 16 | 13 | 15 |
gulphtml
配置簡單,插件豐富,上手快前端
解決了 grunt 配置繁瑣,不易維護的問題,經過插件擴展進一步提升了開發效率vue
webpackjava
解決模塊自由引入,打包node
webpack 解決了 sf 後臺存在的歷史問題,提升了開發效率react
html 模板、js 模塊的自由引入,再也不依賴配置表jquery
每次添加新模塊,再也不須要加入一堆的 script 標籤webpack
代碼壓縮合並速度提高
coffee 換成了 js(es6)
Webpack 增長了項目開發的靈活性,優化了性能
提取公用 js 代碼
只要有 loader ,能夠自由使用多種語言
支持按需加載
去年在 SF 技術分享會安利過 gulp building width gulp
配置 webpack須要建一個 webpack.config.js 文件。建議搭配 webpack-dev-server 使用:webpack doc webpack-dev-server
咱們的配置文件大概長這個樣子。下面來分析一下。
var webpack = require('webpack'); var reusePlugin = new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', _:'underscore', 'window.jQuery': 'jquery', 'root.jQuery': 'jquery' }); var config = { context: __dirname + '/src', devtool: 'source-map', entry:{ 'chart':[ './chart/ChartCtrl.js', './chart/ChartDirective.js', ], 'log':'./log/ctrl.js', 'dashboard':'./dashboard/ctrl.js', 'operation':'./operation/ctrl.js' }, output:{ path: __dirname + '/dist', filename:'./[name]/ctrl.js', publicPath:'/static/dist/', }, devServer: { hot: true, port: 3333, proxy: { '*': { target: 'http://xxadmin.domain.com', secure: false, changeOrigin: true }, }, }, module:{ loaders:[ { test: /\.js$/, exclude: /(node_modules|bower_components|3rd)/, loader: 'babel', // 'babel-loader' is also a legal name to reference query: { presets: ['es2015'] } }, { test: /\.html$/, loader: "html" }, { test: /\.css$/, exclude: /(node_modules|bower_components|3rd)/, loader: "style-loader!css-loader" }, { test: /\.scss$/, loaders: ["style", "css", "sass"] }, { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' } ] }, plugins: [ reusePlugin, ] }; module.exports = config;
處理的入口,配置須要處理的 js。entry 有三種寫法,每一個入口稱爲一個chunk。
字符串 entry: "./index/index.js"
配置模塊會被解析爲模塊,並在啓動時加載。默認 chunk 名爲 main, 具體打包文件名可在 output 中配置。
數組 entry: ['./src/mod1.js', [...,] './src/index.js']
全部的模塊會在啓動時 按照配置順序 加載,合併到最後一個模塊會被導出。默認 chunk 名也是 main。
對象 entry: {index: '...', login : [...] }
傳入Object,則會生成多個入口打包文件, key 是 chunk 名,value能夠是字符串,也但是數組。
很明顯咱們採用的是第三種。
設置入口配置的文件的輸出規則,經過output對象實現
output.path
:輸出文件路徑,一般設置爲 __dirname + ‘/build’
output.filename
:輸出文件名稱,有下面列出的四種可選的變量,filename 配置能夠是這幾種的任意一種或多種的組合
[id] chunk的id
[name] chunk名
[hash] 編譯哈希值
[chunkhash] chunk的hash值
output.publicPath
:設置爲想要的資源訪問路徑。通常使用 webpack-dev-server 時,則須要經過相似 http://localhost:8080/asstes/index-1.js 來訪問資源,若是沒有設置,則默認從站點根目錄加載。
有些時候,咱們用到的第三方庫並無採用 CommonJS 或 AMD 規範。這樣咱們沒法經過 require() 來引用這些庫。
Webpack 給出瞭解決方案,在項目根目錄下,建立一個叫作 web_modules 的文件夾,將須要用到的第三方庫存放到裏面,就能夠在邏輯代碼中使用 require(‘xx-lib.js’)
來引用並使用了。
當咱們常用React、jQuery等外部第三方庫的時候,一般在每一個業務邏輯JS中都會遇到這些庫。
如咱們須要在各個文件中都是有jQuery的$對象,所以咱們須要在每一個用到jQuery的JS文件的頭部經過require('jquery')來依賴jQuery。 這樣作很是繁瑣且重複。
webpack提供了咱們一種比較高效的方法,咱們能夠經過在配置文件中配置使用到的變量名,那麼webpack會自動分析,而且在編譯時幫咱們完成這些依賴的引入。
這樣,咱們在JS中,就不須要引入jQuery等經常使用模塊了,直接使用配置的這些變量,webpack就會自動引入配置的庫。
new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', _:'underscore', 'window.jQuery': 'jquery', 'root.jQuery': 'jquery' });
項目中有些代碼咱們只爲在開發環境(例如日誌)或者是內部測試環境(例如那些沒有發佈的新功能)中使用,又不想讓這些調試內容在發佈的時候泄露出去,那就須要引入下面這些魔法全局變量(magic globals):
if (__DEV__) { console.warn('Extra logging'); } // ... if (__PRERELEASE__) { showSecretFeature(); }
同時還要在webpack.config.js中配置這些變量,使得 webpack 可以識別他們。
// webpack.config.js // definePlugin 會把定義的string 變量插入到Js代碼中。 var definePlugin = new webpack.DefinePlugin({ __DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'true')), __PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false')) }); module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, plugins: [definePlugin] };
配置完成後,就可使用 BUILD_DEV=1 BUILD_PRERELEASE=1 webpack來打包代碼了。 值得注意的是,webpack -p 會刪除全部無做用代碼,也就是說那些包裹在這些全局變量下的代碼塊都會被刪除,這樣就能保證這些代碼不會因發佈上線而泄露。
雖然CommonJS是同步加載的,可是webpack也提供了異步加載的方式。這對於單頁應用中使用的客戶端路由很是有用。當真正路由到了某個頁面的時候,它的代碼纔會被加載下來。
指定你要異步加載的 拆分點。看下面的例子
if (window.location.pathname === '/feed') { showLoadingState(); require.ensure([], function() { // 這個語法痕奇怪,可是仍是能夠起做用的 hideLoadingState(); require('./feed').show(); // 當這個函數被調用的時候,此模塊是必定已經被同步加載下來了 }); } else if (window.location.pathname === '/profile') { showLoadingState(); require.ensure([], function() { hideLoadingState(); require('./profile').show(); }); }
剩下的事就能夠交給webpack,它會爲你生成並加載這些額外的 chunk 文件。
咱們能夠在package.json中事先定義好命令:
"scripts": { "dev": "BUILD_DEV=1 webpack-dev-server --progress --colors", "build": "BUILD_PRERELEASE=1 webpack -p" }
那麼就能夠避免輸入冗長的命令了
開發時輸入 npm run dev
發佈時輸入 npm run build
項目中,對於一些經常使用的組件,站點公用模塊常常須要與其餘邏輯分開,而後合併到同一個文件,以便於長時間的緩存。要實現這一功能,配置參照:
var webpack = require('webpack'); var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin; ... entry: { a: './index/a.js', b: './idnex/b.js', c: './index/c.js', d: './index/d.js' }, ... plugins: [ new CommonsChunkPlugin('part1.js', ['a', 'b']), new CommonsChunkPlugin('common.js', ['part1', 'c']) ]
簡單的狀況下能夠這樣寫 newwebpack.optimize.CommonsChunkPlugin('common.js’);,這樣就會提取全部模塊的通用代碼到 common.js。
能夠經過在配置中加入devtool項,選擇預設調試工具來提升代碼調試質量和效率:
eval – 每一個模塊採用eval和 //@ sourceURL 來執行
source-map – sourceMap是發散的,和output.sourceMapFilename協調使用
hidden-source-map – 和source-map相似,可是不會添加一個打包文件的尾部添加引用註釋
inline-source-map – SourceMap以DataUrl的方式插入打包文件的尾部
eval-source-map – 每一個模塊以eval方式執行而且SourceMap以DataUrl的方式添加進eval
cheap-source-map – 去除column-mappings的SourceMap, 來自於loader中的內容不會被使用。
cheap-module-source-map – 去除column-mappings的SourceMap, 來自於loader中的SourceMaps被簡化爲單個mapping文件
devtool | 構建速度 | 再次構建速度 | 支持發佈版 | 質量 |
---|---|---|---|---|
eval | +++ | +++ | no | 生成代碼 |
cheap-eval-source-map | + | ++ | no | 轉換代碼(lines only) |
cheap-source-map | + | o | yes | 轉換代碼(lines only) |
cheap-module-eval-source-map | o | ++ | no | 源代碼 (lines only) |
cheap-module-source-map | o | – | yes | 源代碼(lines only) |
eval-source-map | — | + | no | 源代碼 |
source-map | — | — | yes | 源代碼 |
來自官方文檔:
Loaders allow you to preprocess files as you require() or 「load」 them. Loaders are kind of like 「tasks」 are in other build tools, and provide a powerful way to handle frontend build steps. Loaders can transform files from a different language like CoffeeScript to JavaScript, or inline images as data URLs. Loaders even allow you to do things like require() css files right in your JavaScript!
module.exports = { entry: ["./global.js" , "./app.js"], output: { filename: "bundle.js" }, module: { loaders: [ { test: /\.es6$/, exclude: /node_modules/, loader: 'babel-loader', query: { presets: ['react', 'es2015'] } } ] }, resolve: { extensions: ['', '.js', '.es6'] }, }
咱們的第一個loader 添加了3個鍵,下面分別作下解釋。
test — 一個正則表達式,測試什麼樣的文件類型能夠經過loader去執行。上面的例子意思是僅後綴爲.es6的文件經過。
exclude — 表示loader 應該忽略/不包含的文件/文件路徑。例如 node_modules 文件夾.
loader —表示咱們正在使用的loader 名稱 (babel-loader).
query — 你能夠傳遞一些選項參數到loader,寫法相似一個 query string 或者像上面的例子那樣使用 query 屬性。
presets —讓咱們可以使用早先安裝好的 react 和 es2015 的 presets。
var webpack = require('webpack'); var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin; var ExtractTextPlugin = require('extract-text-webpack-plugin'); //自定義"魔力"變量 var definePlugin = new webpack.DefinePlugin({ __DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'false')), __PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false')) }); module.exports = { //上下文 context: __dirname + '/src', //配置入口 entry: { a: './view/index/index.js', b: './view/index/b.js', vender: ['./view/index/c.js', './view/index/d.js'] }, //配置輸出 output: { path: __dirname + '/build/', filename: '[name].js?[hash]', publicPath: '/assets/', sourceMapFilename: '[file].map' }, devtool: '#source-map', //模塊 module: { loaders: [ { //處理javascript test: /\.js$/, exclude: /node_modules/, loader: 'babel' }, { test: /\.css$/, loader: ExtractTextPlugin.extract( "style-loader", "css-loader?sourceMap" ) }, { test: /\.less$/, loader: ExtractTextPlugin.extract( "style-loader", "css-loader!less-loader" ) }, { test: /\.(png|jpg)$/, loader: 'url-loader?limit=1024' }, { //處理vue test: /\.vue$/, loader: 'vue-loader' }, { 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' } ] }, plugins: [ //公用模塊 new CommonsChunkPlugin('common.js', ['a', 'b']), //設置抽出css文件名 new ExtractTextPlugin("css/[name].css?[hash]-[chunkhash]-[contenthash]-[name]", { disable: false, allChunks: true }), //定義全局變量 definePlugin, //設置此處,則在JS中不用相似require('./base')引入基礎模塊, 只要直接使用Base變量便可 //此處一般可用作,對經常使用組件,庫的提早設置 new webpack.ProvidePlugin({ Moment: 'moment', //直接從node_modules中獲取 Base: '../../base/index.js' //從文件中獲取 }) ], //添加了此項,則代表從外部引入,內部不會打包合併進去 externals: { jquery: 'window.jQuery', react: 'window.React', //... } };
工欲善其事,必先利其器。