新建一個index.html文件爲做本次項目的承載頁面,內容大體以下:javascript
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link rel="stylesheet" type="text/css" href="build/app.css"> <title>react</title> </head> <body> <div id="app"></div> <script type="text/javascript" src="build/app.js"></script> </body> </html>
新建build目錄,用來存放打包以後的樣式和js文件,新建一個app目錄用來存放源代碼,做爲一個簡單的示例,作這些就夠了,接下來在app目錄下新建main.jsx和main.styl兩個文件,main.jsx們將被做爲整個項目的入口。這個文件的業務邏輯不重要,咱們此次不是要真的製做一個吊炸天的項目,只是爲了演示如何利用webpack及一些周邊插件實現前端開發的自動化構建。固然,爲了寫的點的趣味性,我引入一下react.js來助助興。下面是它的源碼:css
'use strict'; import React from "react"; import AboutUs from "./about.jsx"; import ReactDOM from "react-dom"; function bootstrap(){ var initialState = window.list; ReactDOM.render(<AboutUs initialState={initialState} />,document.getElementById('app')); } if(typeof window.addEventListener){ window.addEventListener("DOMContentLoaded",bootstrap); }else{ window.attachEvent('onload',bootstrap); }
react的亮點之一就是它的組件化開發,然而我也不打算在這裏免費幫它作宣傳。我在這裏建立了一個叫做about.jsx的組件,主要是爲了使得本次演示能儘量的豐滿一點。順便貼一下about.jsx的源碼:html
'use strict' import React,{Component} from "react"; class AboutUs extends Component{ constructor(props){ super(props); this.state = { maskActive:false, pageIndex:1 } this.handleClick = this.handleClick.bind(this); } handleClick(){ var pageIndex = this.state.pageIndex+1; this.setState({ pageIndex, maskActive:true }); } memuList(){ let list = this.props.initialState||[]; return list.map((item,i)=>{ return (<li key={'i-'+i} onClick={this.handleClick}>{item.name}</li>) }); } render(){ const {pageIndex,maskActive} = this.state; let maxlength = Math.min(pageIndex * 10,window.innerWidth); let proces = {width:(maxlength) + 'px','textIndent':maxlength+'px'}; return ( <div className="aboutus-content"> <h3> <span className="title">關於咱們</span> </h3> <ul> {this.memuList()} </ul> <div className="process"> <div style={proces}>{maxlength}</div> </div> <footer> copyright@2014-2016 湖南長沙互聯網家 </footer> </div> ) } } export default AboutUs;
請忽視裏邊的邏輯,我認可寫的確實有點無厘頭。爲了讓頁面不至於太倉白,來一個樣式潤下色,因此main.styl就應聲出場了,源碼以下:前端
html { height: 100%; } body { font-size: 14px; -webkit-user-select:none; } ul { list-style-type:none; display: flex; margin: 0; padding: 0; } li { line-height: 1.2rem; padding: 1rem 2rem; background-color: #884809; border-right: 1px solid wheat; color: white; } footer { display: flex; height: 40px; color: black; line-height: 40px; } .process { height: 40px; width: 100%; line-height: 40px; border: 1px solid gray; } .process div { max-width: 99%; background-color: green; height: 100%; }
嗯,也沒有什麼出奇的地方,甚至連sass的語法都沒有,惟一個免強能拿的出手就是這個flex,在將man.styl轉成app.css以後,會自動補上瀏覽器的私有前綴。固然,若是你要在此放一個less/sass的彩蛋,我不反對。爲了緊扣主題,下面個人重點工做要開始了java
。在test/目錄下新建一個webpack.config.js的文件,我寫的內容是這樣的:node
var path = require('path'); var webpack = require('webpack'); var nodeModulesPath = path.resolve(__dirname, 'node_modules'); var ExtractTextPlugin = require("extract-text-webpack-plugin"); var entry = require('./config.js'); module.exports = { entry: entry, resolve: { extentions: ["", "js", "jsx"] }, module: { loaders: [{ test: /\.(es6|jsx)$/, exclude: nodeModulesPath, loader: 'babel-loader', query: { presets: ['react', 'es2015','stage-2'] } }, { test: /\.styl/, exclude: [nodeModulesPath], loader: ExtractTextPlugin.extract('style', 'css!autoprefixer!stylus') }] }, output: { path: path.resolve(__dirname, './build'), publicPath:'/build/', filename: './[name].js', }, plugins: [ new webpack.NoErrorsPlugin(), new ExtractTextPlugin("./[name].css") ] };
爲了在生產環境和開發環境複用代碼,我獨立出一個叫做config.js的文件,內容以下:react
module.exports ={
app:['./app/main.jsx','./app/main.styl']
}webpack
再接下來是時候修改一下package.json文件了git
{ "name": "s-react", "version": "1.0.0", "description": "React is a JavaScript library for building user interfaces.", "main": "index.js", "directories": { "example": "how to use react with webpack" }, "scripts": {"dev": "webpack-dev-server --devtool eval --inline --hot --port 3000","build": "webpack --progress --colors --display-error-details", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "278500368@qq.com", "repository": "https://github.com/bjtqti/study", "license": "MIT", "dependencies": { "react": "^15.3.2", "react-dom": "^15.3.2" }, "devDependencies": { "autoprefixer-loader": "^3.2.0", "babel-core": "^6.17.0", "babel-loader": "^6.2.5", "babel-preset-es2015": "^6.16.0", "babel-preset-react": "^6.16.0", "babel-preset-stage-2": "^6.17.0", "clean-webpack-plugin": "^0.1.13", "css-loader": "^0.25.0", "extract-text-webpack-plugin": "^1.0.1", "style-loader": "^0.13.1", "stylus": "^0.54.5", "stylus-loader": "^2.3.1", "webpack": "^1.13.2", "webpack-dev-server": "^1.16.2" } }
重點關注一scripts裏邊的內容,dev的做用是生成一個web開發服務器,經過localhost:3000就能夠當即看到頁面效果,關於webpack-dev-server 的使用,網上介紹的不少,我這裏着重要強調的就是--hot --inline 的使用,它使得咱們以最簡單的方式實現了瀏覽器的自動刷新和代碼的熱替換功能。 固然,還有一種叫做iframe的模式,不過訪問地址要做修改,好比http://localhost:3000/webpack-dev-server/index.html
. 我我的不太喜歡,因而採用了inline的方式。es6
除了使用CLI的方式以外,還有一種方式,在網上也介紹的不少,不過由於相比CLI方式來講,要繁鎖的多,因此也更容易讓初學者遇到問題,可是它的可配置性更高,針對一些個性化的需求,它可能更容易達成你想要的效果。因此有必要順帶介紹一下這種方式.
首頁在test目錄下新建一個server.js的文件(名字能夠隨意)
var config = require("./webpack.config.js"); var webpack = require("webpack"); var webpackDevServer = require('webpack-dev-server'); var compiler = webpack(config); var server = new webpackDevServer(compiler, { hot: true, inline: true, // noInfo: true, publicPath: '/build/', watchOptions: { aggregateTimeout: 300, poll: 1000 }, // historyApiFallback: true }); server.listen(3000, "localhost", function(err, res) { if (err) { console.log(err); } console.log('hmr-server Listening at http://%s:%d','localhost', 3000); });
因爲咱們不打算用CLI方式,因此--hot -- inline這個參數就移到了這個配置裏邊來了,用這種方式比較煩人的地方就是webpack.config.js的entry要作很大的改動,好比
entry: {
app:["webpack-dev-server/client?http://localhost:3000/", "webpack/hot/dev-server",'./app/main.jsx','./asset/main.styl']
}
若是app有多項,那麼勢必要寫一個循環來添加,再若是咱們改了一下webpack-dev-server的端口,這裏邊也要修改,除非把端口號做爲一個變量進行拼接。正當你滿懷信心,準備見證奇蹟的時候,等來的確是奇怪,瀏覽器的控制檯怎麼報錯了。緣由在於webpack.config.js中的plugs中要加上new webpack.HotModuleReplacementPlugin():
var path = require('path'); var webpack = require('webpack'); var nodeModulesPath = path.resolve(__dirname, 'node_modules'); var ExtractTextPlugin = require("extract-text-webpack-plugin"); var entry = require('./config.js'); entry.app.unshift("webpack-dev-server/client?http://localhost:3000/", "webpack/hot/dev-server"); module.exports = { entry: entry, resolve: { extentions: ["", "js", "jsx"] }, module: { loaders: [{ test: /\.(es6|jsx)$/, exclude: nodeModulesPath, loader: 'babel-loader', query: { presets: ['react', 'es2015','stage-2'] } }, { test: /\.styl/, exclude: [nodeModulesPath], loader: ExtractTextPlugin.extract('style', 'css!autoprefixer!stylus') }] }, output: { path: path.resolve(__dirname, './build'), publicPath:'/build/', filename: './[name].js', }, plugins: [ new webpack.NoErrorsPlugin(), new webpack.HotModuleReplacementPlugin(), new ExtractTextPlugin("./[name].css") ] };
而後咱們運行npm run server 實現了和CLI 方式同樣的效果。改一改main.styl,頁面樣式也同步更新,沒有手動刷新形成的白屏現象,更新main.jsx也是一樣的自動更新了,就感受和ajax的效果同樣。今後改一下代碼按一下F5的時代結束了。
最後就是要打包出生產環境所須要的js和css代碼,這個相對就簡單了許多,只要把webpack.config.js另存爲webpack.develop.config.js(名字隨意),而後進去改改就行了:
var path = require('path'); var webpack = require('webpack'); var nodeModulesPath = path.resolve(__dirname, 'node_modules'); var ExtractTextPlugin = require("extract-text-webpack-plugin"); var CleanPlugin = require('clean-webpack-plugin'); var entry = require('./config.js'); module.exports = { entry: entry, resolve:{ extentions:["","js"] }, module: { loaders: [{ test: /\.jsx?$/, exclude: nodeModulesPath, loader: 'babel-loader', query: { presets: ['react','es2015'] } },{ test: /\.styl/, exclude: [nodeModulesPath], loader: ExtractTextPlugin.extract('style', 'css!autoprefixer!stylus') }] }, output: { path: path.resolve(__dirname, './dest'), filename: '[name]-[hash:8].min.js', }, plugins: [ new CleanPlugin('builds'), new ExtractTextPlugin("./[name]-[hash:8].css"), new webpack.DefinePlugin({ 'process.env': {NODE_ENV: JSON.stringify('production')} }), new webpack.optimize.DedupePlugin(), new webpack.optimize.OccurenceOrderPlugin(true), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false }, output: { comments: false }, sourceMap: false }) ] };
更多的是plugins裏邊,多了一些優化的插件,好比合並,壓縮,加上hash值爲做版本號,上面的配置中我用[hash:8]截取前8位做爲版本號。須要重點提一下就是 extract-text-webpack-plugin 這個插件的使用,它可使css文件打包成獨立的文件,而不是做爲js的一部分混在app.js裏邊,它的使用須要注意兩個地方:1是在loader中的寫法loader: ExtractTextPlugin.extract('style', 'css!autoprefixer!stylus')
而後就是在plugins中也要加上,若是是動態name的,要寫成[name]
new ExtractTextPlugin("./[name]-[hash:8].css"), 這個和output中的寫法是對應的。而後咱們在package.json的scripts中,增長一個"release": "webpack --config webpack.develop.config.js --display-error-details"
保存,運行npm run release --production就能夠看到打包以後的文件了,爲了區別開發環境的打包,我這裏指定dest目錄下爲生產生境下的打包輸出。
最後預覽一下成果:
因爲使用了webpack-dev-server開發,代碼是保存在內存中,在瀏覽器的控制面版的source中,只有通過webpack生成以後的js代碼,好比像下面這樣的狀況:
/******/ (function(modules) { // webpackBootstrap /******/ var parentHotUpdateCallback = this["webpackHotUpdate"]; /******/ this["webpackHotUpdate"] = /******/ function webpackHotUpdateCallback(chunkId, moreModules) { // eslint-disable-line no-unused-vars /******/ hotAddUpdateChunk(chunkId, moreModules); /******/ if(parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules); /******/ } /******/ /******/ function hotDownloadUpdateChunk(chunkId) { // eslint-disable-line no-unused-vars /******/ var head = document.getElementsByTagName("head")[0]; /******/ var script = document.createElement("script"); /******/ script.type = "text/javascript"; /******/ script.charset = "utf-8"; /******/ script.src = __webpack_require__.p + "" + chunkId + "." + hotCurrentHash + ".hot-update.js"; /******/ head.appendChild(script); /******/ } /******/ /******/ function hotDownloadManifest(callback) { // eslint-disable-line no-unused-vars /******/ if(typeof XMLHttpRequest === "undefined") /******/ return callback(new Error("No browser support")); ......
而手寫的代碼是這樣的
'use strict' import React,{Component} from "react"; class AboutUs extends Component{ constructor(props){ super(props); this.state = { maskActive:false, pageIndex:1 } this.handleClick = this.handleClick.bind(this); } handleClick(){ var pageIndex = this.state.pageIndex+1; this.setState({ pageIndex, maskActive:true }); } memuList(){ let list = this.props.initialState||[]; return list.map((item,i)=>{ return (<li key={'i-'+i} onClick={this.handleClick}>{item.name}</li>) }); } render(){
這時就須要用到devtool這個配置項,有兩種開啓方式,對應於CLI方式,只要在webpack-dev-server 後加上 --devtool source-map 就能夠了。對應 webpack.config.js中的方式,則是增長devtool :"source-map" 這一項。兩種試,任選其一便可。
完成這一步以後,從新啓動webpack-dev-server,而後在瀏覽器中,打開控制檯,這時,在source選項卡中,會多出一個webpack://的內容,而後找到要斷點執行的代碼,就能夠執行斷點調試了。截圖以下:
關於devtool的選項,官網還有其它幾個值,我只用到source-map就知足需求,其它項未作實踐。
1. 爲了方便css文件的統一管理,我把它們通通放在entry中,而網上大都是介紹在js中用require('xxx.css') 的方式,我以爲單獨在entry中引入更加清晰。
2. 爲了重點演示自動化構建,關鍵點有兩個地方,1是將代碼進行轉化(es6,jsx,less),打包輸出,2是代碼的熱替換和自動刷新 ,若是有node的部分,還要作node進程的自動重啓。
3. 若是是簡單的需求,使用CLI方式比較簡單省事。
4. 自動化構建看起來很簡單,要系統的掌握並應用到實際開發中,還須要多加實踐。
最後附上本例的全部源碼,方便有須要的同窗下載測試,也歡迎提出指導意見