webpack 打包 + es6 + react入門
本文主要針對webpack打包流程,注意事項,es6新特性變化,以及es6 開發react的入門教程:
- webpack打包流程
- webpack注意事項
- webpack,gulp,grunt差異對比
- webpack進階:關于加載優化與緩存
- es6 新标準及新特性
- es6 核心特性分析
- react 基本文法
- react 生命周期
- react 父子元件的通信總結
【第一部分】Webpack入門
webpack打包流程
React開發和子產品管理的主流工具webpack也被大家所熟知。
web開發中常用到的靜态資源主要有JavaScript、CSS、圖檔、Jade等檔案,webpack中将靜态資源檔案稱之為子產品。webpack是一個module bundler(子產品打包工具),其可以相容多種js書寫規範,且可以處理子產品間的依賴關系,具有更強大的js子產品化的功能。

webpack特性
webpack具有requireJs和browserify的功能,但仍有很多自己的新特性:
序号 | 特點 | 備注 |
---|---|---|
1 | 對 CommonJS 、 AMD 、ES6的文法做了相容 | 相容性強 |
2 | 對js、css、圖檔等資源檔案都支援打包 | 打包範圍廣 |
3 | 串聯式子產品加載器以及插件機制,讓其具有更好的靈活性和擴充性,例如提供對CoffeeScript、ES6的支援 | 靈活易擴充 |
4 | 有獨立的配置檔案webpack.config.js | 獨立配置,高内聚 |
5 | 可以将代碼切割成不同的chunk,實作按需加載,降低了初始化時間 | 按需,高效,低耦合 |
6 | 支援 SourceUrls 和 SourceMaps,易于調試 | [不很懂]就是即使代碼壓縮,也可以轉化為非混淆壓縮形式調試 |
7 | 具有強大的Plugin接口,大多是内部插件,使用起來比較靈活 | 靈活 |
8 | webpack 使用異步 IO 并具有多級緩存。這使得 webpack 很快且在增量編譯上更加快 | 異步,緩存,性能優 |
1.相比Grunt,WebPack除了具備豐富的插件外,同時帶有一套加載(Loader)系統。使它支援多種規範的加載方式,包括ES6、CommonJS、AMD等方式,這是Grunt、Gulp所不具備的。
2.代碼混淆的角度來看,WebPack更加的極緻
3.代碼分片為處理單元(而不是檔案),使得檔案的分片更為靈活。
Webpack安裝和配置
- 1.安裝
- webpack 可以作為 全局的npm子產品安裝,也可以在 目前項目中安裝。
npm install -g webpack //全局安裝
npm install --save-dev webpack //産品模式用dependencies,開發模式用devDep; save就是存到依賴清單裡package.json
全局安裝的webpack,直接執行webpack指令,會預設使用目前目錄的webpack.config.js作為配置檔案。如果要指定另外的配置檔案,可以執行:
webpack -config webpack.chauncey.config.js //指定新檔案
- 配置
- 每個項目下需配置一個webpack.config.js, 如同gulpfile.js/Gruntfile.js, 通俗一點,他就是你的一個初始化設定,配置項,告訴webpack,你想怎麼去處理你的檔案。
-
環境變量
通過NODE_ENV可以來設定環境變量(預設值為development)。區分開發和生産環境,對于檔案的不同處理。
linux & mac: export NODE_ENV=production
windows:set NODE_ENV=production
運作前可以設定目前環境,SET NODE_ENV=production(開發環境)
set PORT=1234設定接口
-
entry
entry參數: 打包的輸入檔案,參數值代表輸出後檔案形式,字元串(單個檔案)、數組(一大個檔案)、對象(不同屬性為不同檔案)
{
entry: {
page1: "./page1",
//支援數組形式,将加載數組中的所有子產品,但以最後一個子產品作為輸出
page2: ["./entry1", "./entry2"],
page3: {
subp1: "./sp1",
subp2: "./sp2"
}
},
output: {
path: "dist/js/page",//目标目錄【檔案夾】
publicPath: "/output/",
filename: "[name].bundle.js"
}
}
output
output參數為對象,定義了輸出檔案的位置及名字
output: {
path: "dist/js/page",//打封包件存放的絕對路徑
publicPath: "/output/",//網站運作時的通路路徑
filename: "[name].bundle.js"//打包後的檔案名
}
當我們在entry中定義建構多個檔案時,filename可以對應的更改為[name].js用于定義不同檔案建構後的名字.
module
在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(加載器);[可參考using loaders]
! 用來定義loader的串聯關系,”-loader”是可以省略不寫的,多個loader之間用“!”連接配接起來,但所有的加載器都需要通過 npm 來加載。
此外,還可以添加用來定義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配置項。
resolve
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 第一個是空字元串! 對應不需要字尾的情況.
plugin
webpack提供了[豐富的元件]用來滿足不同的需求,當然我們也可以自行實作一個元件來滿足自己的需求:
plugins: [
//your plugins list
]
在webpack中編寫js檔案時,可以通過require的方式引入其他的靜态資源,可通過loader對檔案自動解析并打封包件。通常會将js檔案打包合并,css檔案會在頁面的header中嵌入style的方式載入頁面。但開發過程中我們并不想将樣式打在腳本中,最好可以獨立生成css檔案,以外鍊的形式加載。這時extract-text-webpack-plugin插件可以幫我們達到想要的效果。需要使用npm的方式加載插件,然後參見下面的配置,就可以将js中的css檔案提取,并以指定的檔案名來進行加載。
plugins: [
new ExtractTextPlugin('styles.css')
]
externals
當我們想在項目中require一些其他的外部類庫或者API,而又不想讓這些類庫的源碼被建構到運作時檔案中,這在實際開發中很有必要。此時我們就可以通過配置externals參數來解決這個問題:
externals: {
"jquery": "jQuery"
}
這樣我們就可以放心的在項目中使用這些API了:var jQuery = require(“jquery”);
context
當我們在require一個子產品的時候,如果在require中包含變量,像這樣:
require("./mods/" + name + ".js");
那麼在編譯的時候我們是不能知道具體的子產品的。但這個時候,webpack也會為我們做些分析工作:
1.分析目錄:’./mods’;
2.提取正規表達式:’/^.*.js$/’;
于是這個時候為了更好地配合webpack進行編譯,我們可以給它指明路徑,像在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常用指令
webpack的使用通常有三種方式:
1、指令行使用:webpack
var webpack = require('webpack');
webpack({
//configuration
}, function(err, stats){});
3、預設使用目前目錄的webpack.config.js作為配置檔案。如果要指定另外的配置檔案,可以執行:webpack –config webpack.custom.config.js
webpack 的執行也很簡單,直接執行
即可,後面的參數“–display-error-details”是推薦加上的,友善出錯時能查閱更詳盡的資訊(比如 webpack 尋找子產品的過程),進而更好定位到問題。
常用指令
webpack的使用和browserify有些類似,下面列舉幾個常用指令:
即可,後面的參數“--display-error-details”是推薦加上的,友善出錯時能查閱更詳盡的資訊(比如 webpack 尋找子產品的過程),進而更好定位到問題。
常用指令
webpack的使用和browserify有些類似,下面列舉幾個常用指令:
前面的四個指令比較基礎,使用頻率會比較大,後面的指令主要是用來定位打包時間較長的原因,友善改進配置檔案,提高打包效率。
圖檔打包和靜态資源伺服器
圖檔打包
webpack中對于圖檔的處理,可以通過url-loader來實作圖檔的壓縮。
div.img{
background: url(../image/xxx.jpg)
}
//或者
var img = document.createElement("img");
img.src = require("../image/xxx.jpg");
document.body.appendChild(img);
針對上面的兩種使用方式,loader可以自動識别并處理。根據loader中的設定,webpack會将小于指點大小的檔案轉化成 base64 格式的 dataUrl,其他圖檔會做适當的壓縮并存放在指定目錄中。
module: {
{
test: /\.(png|jpg)$/,
loader: 'url-loader?limit=10000&name=build/[name].[ext]'
}]
}
對于上面的配置,如果圖檔資源小于10kb就會轉化成 base64 格式的 dataUrl,其他的圖檔會存放在build/檔案夾下。
靜态資源伺服器
除了提供子產品打包功能,Webpack還提供了一個基于Node.js Express架構的開發伺服器,它是一個靜态資源Web伺服器,對于簡單靜态頁面或者僅依賴于獨立服務的前端頁面,都可以直接使用這個開發伺服器進行開發。在開發過程中,開發伺服器會監聽每一個檔案的變化,進行實時打包,并且可以推送通知前端頁面代碼發生了變化,進而可以實作頁面的自動重新整理。
Webpack開發伺服器需要單獨安裝,同樣是通過npm進行:
可以使用webpack-dev-server直接啟動,也可以增加參數來擷取更多的功能,具體配置可以參見官方文檔。預設啟動端口8080,通過localhost:8080/webpack-dev-server/可以通路頁面,檔案修改後儲存時會在頁面頭部看到sever的狀态變化,并且會進行熱替換,實作頁面的自動重新整理。
Webpack注意事項-Tips
1.建議,首頁盡量做到最精簡,畢竟決定使用者存留時間。按需。
2.【推薦很好】http://www.cnblogs.com/giveiris/p/5237080.html
【前端建構】WebPack執行個體與前端性能優化
3.【有雪碧圖,gulp和webpack】【基于webpack搭建純靜态頁面型前端工程解決方案模闆】https://github.com/chemdemo/webpack-seed
4.所有資源都是子產品。
Webpack、gulp、grunt異同
webpack實踐
webpack安裝
npm install webpack -g
webpack配置
// webpack.config.js
var path = require("path");
module.exports = {
entry: './public/js/entry.js', //示範單入口檔案
output: {
path: path.join(__dirname, './public/pro'), //打包輸出的路徑
filename: 'bundle.js', //打包後的名字
publicPath: "./pro" //html引用路徑,在這裡是本地位址。
}
};
編寫入口檔案
接下來就編寫我們的入口檔案 entry.js 和第一個子產品檔案 module1.js 。我們一切從簡,裡面隻用來加載一個Js子產品。
// entry.js
require("./module1"); // 使用CommonJs來加載子產品
// module1.js
console.log("hello,world! module1");
document.write("超哥威武");
啟動webpack
一切準備好後,我們僅需要在項目根目錄下,用指令行 webpack 執行一下即可。
// webpack 指令行的幾種基本指令
$ webpack // 最基本的啟動webpack方法
$ webpack -w // 提供watch方法,實時進行打包更新
$ webpack -p // 對打包後的檔案進行壓縮,提供production
$ webpack -d // 提供source map,友善調試。
多子產品依賴
跑通webpack打包entry,現在嘗試commonjs和amd,兩種子產品機制。
require(["./module3"], function(){
console.log("hello,world! module1&&require(module3)");
document.write("超哥威武");
});
// 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(, ));
})
混用兩種不同機制非常不好,這裡僅僅是展示用的;
在開發新項目時還是推薦CommonJs或ES2015的Module。ES2015的子產品機制,是趨勢。【這一塊我會報個小錯誤,沒有完全實作】
【loader加載器】
預處理器做一些CoffeeScript 和 Sass 的編譯。我們以前要編譯這些預處理器,用 gulp相對複雜,webpack可以一次性解決!
在這裡我們用Sass和babel編譯ES2015為例子,看一下loader是如何使用的。
安裝loader
配置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"}
]
}
};
【webpack進階:關于加載優化與緩存,及版本控制】
1.uglify
webpack提供插件UglifyJsPlugin,可以優化(支援壓縮、混淆)代碼。
配置以下清單,在混淆代碼時,以下配置的變量,不會被混淆
new webpack.optimize.UglifyJsPlugin({
mangle: {
except: ['$super', '$', 'exports', 'require']
}
})
以上變量‘ super′,‘ ’, ‘exports’ or ‘require’,不會被混淆
example
var UglifyJsPlugin = require("../../node_modules/webpack/lib/optimize/UglifyJsPlugin");
module.exports = {
entry: "./entry.js",
output: {
path: __dirname,
filename: "bundle.js",
},
plugins: [
//使用醜化js插件
new UglifyJsPlugin({
compress: {
warnings: false
},
mangle: {
except: ['$scope', '$']
}
})
]
};
//entry.js
define("entry", function () {
//變量 iabcdef 已引用,混淆
var iabcdef = ;
//變量 $scope 已引用,但不混淆
var $scope = "scope";
document.write("entry module" + iabcdef);
document.write($scope);
//變量 ixzy 未被引用,剔除
var ixzy = ;
});
2.版本控制
對于靜态資源的版本控制,目前微信項目采取辦法是版本号作為請求參數,版本号為釋出日期,但有兩個問題:
1、更新版本時,CDN不能及時更新;
2、沒有發生變更的檔案也被賦上新版本
Webpack的做法是,生成hash,區分檔案。
配置方法
//所有代碼塊添加hash
module.exports = {
entry: "./entry.js",
output: {
path: "assets/[hash]/",
publicPath: "assets/[hash]/",
filename: "bundle.js"
}
};
生成單個代碼塊檔案的hash
配置方法
//單個代碼塊添加hash
module.exports = {
entry: "./entry.js",
output: {
path: "build/",
publicPath: "build/",
chunkFilename: "[id].[hash].bundle.js",
filename: "output.[hash].bundle.js",
}
};
【關于:chunkFilename意義】
http://react-china.org/t/webpack-output-filename-output-chunkfilename/2256
- 3.如何緩存 緩存控制要做到兩件事情,提到緩存命中率
- 對于沒有修改的檔案,從緩存中擷取檔案
- 對于已經修改的檔案,不要從緩存中擷取 圍繞這兩點,演繹出了很多方案,此處列兩種:
- 不處理,等待使用者浏覽器緩存過期,自動更新。這是最偷懶的,命中率低一些,同時可能會出現部分檔案沒有更新,導緻報錯的情況。
- Http頭對檔案設定很大的max-age,例如1年。同時,給每個檔案命名上帶上該檔案的版本号,例如把檔案的hash值做為版本号,topic. ef8bed6c.js。即是讓檔案很長時間不過期。
- 當檔案沒有更新時,使用緩存的檔案自然不會出錯;
- 當檔案已經有更新時,其hash值必然改變,此時檔案名變了,自然不存在此檔案的緩存,于是浏覽器會去加載最新的檔案。
- 從上面的截圖可以看出來,通過WebPack是可以很輕松做到第二點的——隻需要給檔案名配置上[chunkhash:8]即可,其中8是指hash長度為8,預設是16。
output{
path: _dirname + '/release/',
filename: '[chunkhash:8].[name].js',
chunkFilename: '[name].[chunkhash:8].js'
}
P.S.這樣的處理效果已經很好了,但同樣有劣處,即浏覽器給這種緩存方式的緩存容量太少了,隻有12Mb,且不分Host。是以更極緻的做法是以檔案名為Key,檔案内容為value,緩存在localStorage裡,命中則從緩存中取,不命中則去伺服器取,雖然緩存容量也隻有5Mb,但是每個Host是獨享這5Mb的。
4.自動生成頁面
檔案名帶上版本号後,每一次檔案變化,都需要Html檔案裡手動修改引用的檔案名,這種重複工作很瑣碎且容易出錯。html内部檔案的名字如何動态改變。
使用 HtmlWebpackPlugin 和 ExtractTextPlugin 插件可以解決此問題。
生成帶js的頁面
【參考】http://www.cnblogs.com/haogj/p/5160821.html
new HtmlWebpackPlugin({
filename: 'index.html',
template: __dirname + '/public/index.html',
inject: true,//是否injection
chunks: ['page1'],//所要控制的子產品組
//排序
chunksSortMode: function(a, b){
var index = {'page1': },
aI = index[a.origins[].name],
bI = index[b.origins[].name];
return aI&&bI ? bI - aI : -;
}
}),
生成帶css的頁面
new ExtractTextPlugin(“comm.[contenthash:9].css”)
插件介紹到此為止,然而,還有一個關于同步加載和異步加載的問題,否則入口檔案還是會很臃腫。
- 5.同步/異步加載 一個原則是:首屏需要的同步加載,首屏過後才需要的則按需加載(異步)
-
同步的代碼會被合成并且打包在一起;
異步加載的代碼會被分片成一個個chunk,在需要該子產品時再加載,即按需加載。
同步加載過多代碼會造成檔案過大影響加載速度,異步過多則檔案太碎,造成過多的Http請求,同樣影響加載速度。
同步寫法
var TopicItem = require('../topic/topicitem');
異步寫法
require.ensure([], function(){
//to do
});
【遺留問題】
-
webpackJsonp is not defined
是因為commonschunkplugin引用了,但是頁面沒用到。
-
路徑問題:
entry: {
page1: [‘./public/js/entry.js’], //示範單入口檔案 path.join(__dirname, ‘./public/pro’)
page2: [‘./public/js/module1.js’]
},
路徑必須加./相對位址,否則會報錯publick/js**
3.待學習的:
1)webpack在PC項目中的應用
http://web.jobbole.com/85396/
2)基于 Webpack 和 ES6 打造 JavaScript 類庫
http://web.jobbole.com/84858/
3)webpack使用優化
http://web.jobbole.com/84847/
4)徹底解決 webpack 打封包件體積過大
http://www.jianshu.com/p/a64735eb0e2b
5)大公司裡怎樣開發和部署前端代碼
https://github.com/fouber/blog/issues/6