webpack 單頁面應用實戰

這篇文章將介紹如何利用 webpack 進行單頁面應用的開發,算是我在實際開發中的一些心得和體會,在這裏給你們作一個分享。webpack 的介紹這裏就很少說了,能夠直接去官網查看。 關於這個單頁面應用你們能夠直接去個人github上查看https://github.com/huangshuwei/webpackForSPA,我將結合這個項目去介紹。若是你們以爲這篇文章有不妥的地方,還請指出。javascript

這篇文章的目的是解決咱們在開發中會遇到的問題,不是一篇基礎教程,還請諒解。css

項目目錄

我將根據這個目錄結構進行講解html

  • dist:發佈的文件目錄,即webpack編譯輸出的目錄java

  • libs:放置公共的文件,如js、css、img、font等node

  • mockServer:模擬後端服務,即用webpack開發時模擬調用的後端服務(用nodejs服務模擬)react

  • node_modules:項目依賴的包jquery

  • src:資源文件,裏面包含css、font、html、img、jswebpack

  • package.json:項目配置git

  • webpack.config.js:webpack的配置文件github

項目的使用

建議先運行一下這個項目,有一個大體的瞭解,再往下閱讀。使用說明:

首先克隆一份到你的本地
$ git clone https://github.com/huangshuwei/webpackForSPA.git 而後 cd 到 ‘webpackForSPA’目錄下 $ cd webpackForSPA 接着你能夠運行不一樣的命令查看結果 發佈模式: $ npm run build 開發模式: $ npm run dev 熱更新模式 $ npm run dev-hrm 若是使用了熱更新模式,而且想要結合後端服務形式運行,那麼cd 到‘mockServer’目錄下,並執行node 服務: $ cd mockServer $ node server.js 

區分開發、熱更新、發佈模式

通常開發時和發佈時是不一樣的,好比開發時文件的訪問目錄包含‘dist’目錄,可是發佈上線時,通常會把‘dist’文件夾去掉。固然還有其餘的一些細節不一樣。

開發模式:

  • 能看到webpack編譯輸出的文件

  • js、css、html文件不須要壓縮

  • 能夠正確的運行編譯輸出後的文件

  • 這種模式通常只是用來看webpack編譯輸出後的文件是否正確

熱更新模式:

  • 看不到webpack編譯輸出的文件

  • js、css、html文件不須要壓縮

  • 更改完文件後無需從新編譯並自動刷新瀏覽器

  • 能夠結合後端服務開發,避過瀏覽器同源策略,如結合java、.net服務等

發佈模式:

  • 能看到webpack編譯輸出的文件

  • js、css、html文件壓縮

  • 文件的層級目錄不須要包含‘dist’目錄

我區分開發、熱更新、發佈模式是經過配置‘package.json’文件的運行命令,有些人是經過建立多個不一樣的webpack的配置文件來達到想要的效果。

這個項目就是使用了多個webpack的配置文件。

配置命令

這是在 package.json 文件中配置的

// package.json 文件 ... "scripts": { "build": "webpack --profile --progress --colors --display-error-details", "dev": "webpack --display-modules --profile --progress --colors --display-error-details", "dev-hrm": "webpack-dev-server --config" }, ...
  • color 輸出結果帶彩色,好比:會用紅色顯示耗時較長的步驟

  • profile 輸出性能數據,能夠看到每一步的耗時

  • progress 輸出當前編譯的進度,以百分比的形式呈現

  • display-modules 默認狀況下 node_modules 下的模塊會被隱藏,加上這個參數能夠顯示這些被隱藏的模塊

  • display-error-details 輸出詳細的錯誤信息

  • webpack-dev-server 將會開啓熱更新

  • 更多請參考官網 cli

配置好了package.json文件,咱們就能夠這樣運行

// 開發模式 npm run dev // 熱更新模式 npm run dev-hrm // 發佈模式 npm run build

配置變量標識

配置完了命令,當咱們運行不一樣的命令時,咱們能夠經過‘process.env.npm_lifecycle_event’去獲取當前運行的命令,根據不一樣的命令,咱們能夠按照本身的須要作相應的處理。好比開發模式時,容許開啓調試,靜態資源不要壓縮;發佈模式時,不容許調試,靜態資源要壓縮。具體以下:

// webpack.config.js // 獲取當前運行的模式 var currentTarget = process.env.npm_lifecycle_event; var debug, // 是不是調試 devServer, // 是不是熱更新模式 minimize; // 是否須要壓縮 if (currentTarget == "build") { // 發佈模式 debug = false, devServer = false, minimize = true; } else if (currentTarget == "dev") { // 開發模式 debug = true, devServer = false, minimize = false; } else if (currentTarget == "dev-hrm") { // 熱更新模式 debug = true, devServer = true, minimize = false; }

基礎配置

配置路徑

爲了方便咱們頻繁使用路徑,以下配置

// webpack.config.js var PATHS = { // 發佈目錄 publicPath: debug ? '/webpackForSPA/dist/' : '/webpackForSPA/', // 公共資源目錄 libsPath: path.resolve(process.cwd(), './libs'), // src 資源目錄 srcPath: path.resolve(process.cwd(), 'src'), }

配置別名

webpack的別名的目的就是簡化咱們的操做,引用資源時直接使用別名便可(和 seajs 裏的別名用法同樣)。配置以下:

// webpack.config.js ... resolve:{ alias: { // js jquery: path.join(PATHS.libsPath, "js/jquery/jquery"), underscore: path.join(PATHS.libsPath, "js/underscore/underscore.js"), // css bootstrapcss: path.join(PATHS.libsPath, "css/bootstrap/bootstrap-3.3.5.css"), indexcss: path.join(PATHS.srcPath, "css/index.css"), } } ...

配置webpack編譯入口

// webpack.config.js ... entry:{ // 入口 js index: './src/js/index.js', // 公共js包含的文件 common: [ path.join(PATHS.libsPath, "js/jquery/jquery.js"), path.join(PATHS.libsPath, "js/underscore/underscore.js") ], } ...

配置webpack編譯輸出

// webpack.config.js ... output:{ // 輸出目錄 path: path.join(__dirname, 'dist'), // 發佈後,資源的引用目錄 publicPath: PATHS.publicPath, // 文件名稱 filename: 'js/[name].js', // 按需加載模塊時輸出的文件名稱 chunkFilename: 'js/[name].js' } ...

提取css到單獨的文件

當咱們在js文件中經過require('')引用js時,webpack 默認會將css文件與當前js文件打包一塊兒,可是這種方式會阻塞頁面的加載,由於css的執行要等待js文件加載進來。因此咱們會把css從js文件中提取出來,放到一個單獨的css文件中。這時咱們要使用webpack的插件:extract-text-webpack-plugin,配置以下:

引入插件

// webpack.config.js var ExtractTextPlugin = require("extract-text-webpack-plugin");

配置 loader

// webpack.config.js ... loaders: [ { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader!postcss-loader") }, ... ] ...

配置 plugins

// webpack.config.js ... plugins:[ new ExtractTextPlugin("css/[name].css", {allChunks: true}), ... ] ...

公共js打包

項目中,咱們一般會有公共的js,好比 jquery、bootstrap、underscore 等,那麼這時候咱們須要將這些公共的js單獨打包。這時咱們須要用webpack自帶的插件:

// webpack.config.js ... plugins:[ // 會把 ‘entry’ 定義的 common 對應的兩個js 打包爲 ‘common.js’ new webpack.optimize.CommonsChunkPlugin("common", 'js/[name].js', Infinity), ] ...

資源添加版本號

項目上線後,資源的版本號十分重要。資源沒有版本號,即便從新發布,客戶端瀏覽器可能會把老的資源緩存下來,致使沒法下載最新的資源。webpack 支持給資源添加版本號,不只僅是js、css,甚至font、img均可以添加版本號。咱們能夠經過webpack中的‘chunkhash’來解決。

首先要了解下webpack 中 [hash]、[chunkhash]、[chunkhash:8]的區別。

  • [hash]:webpack編譯會產生一個hash值

  • [chunkhash]:每一個模塊的hash值

  • [chunkhash:8]:取[chunkhash]的前8位

推薦發佈模式使用版本號,其餘模式無需使用,熱更新模式不支持‘chunkhash’,可是支持‘hash’

資源加版本號,那麼咱們的輸出的部分都要作改動,而且要區分當前的命令模式,以下:

// webpack.config.js ... output:{ // 輸出目錄 path: path.join(__dirname, 'dist'), // 發佈後,資源的引用目錄 publicPath: PATHS.publicPath, // 文件名稱 filename: devServer ? 'js/[name].js' : 'js/[name]-[chunkhash:8].js', // 按需加載模塊時輸出的文件名稱 chunkFilename: devServer ? 'js/[name].js' : 'js/[name]-[chunkhash:8].js' } ...

輸出公共js的地方也要改動:

// webpack.config.js ... plugins:[ // 會把 ‘entry’ 定義的 common 對應的兩個js 打包爲 ‘common.js’ new webpack.optimize.CommonsChunkPlugin("common", "" + (devServer ? 'js/[name].js' : "js/[name]-[chunkhash:8].js"), Infinity), ] ...

頁面自動引入含有版本號的文件

有個版本號後,咱們考慮如何經過html引用這些含有版本號的js、css、font、img。webpack每次編譯後的資源 chunkhash 會隨着內容的變化而變化,因此咱們不可能每次都手動的更改html這些資源的引用路徑。這時咱們要用到webpack的插件:html-webpack-plugin。這個插件的目的是生成html,也能夠根據模板生成html,固然還有其餘的功能,具體看插件介紹。下面是的配置:

引入插件

// webpack.config.js var HtmlWebpackPlugin = require('html-webpack-plugin');

配置 plugins,生成須要的html

// webpack.config.js ... plugins:[ new HtmlWebpackPlugin({ filename: 'index.html', template: __dirname + '/src/index.html', inject: 'true' }), new HtmlWebpackPlugin({ filename: 'html/hrm.html', template: __dirname + '/src/html/hrm.html', inject: false, }), new HtmlWebpackPlugin({ filename: 'html/home.html', template: __dirname + '/src/html/home.html', inject: false, }), ] ...

咱們前面說過,webpack 默認只識別 js 文件,因此對於html也要使用對應的loader:

// webpack.config.js ... loaders:[ {test: /\.html$/,loader: "html"}, ] ...

引用圖片和字體

引用圖片和字體,須要對應的loader,而且能夠設置這些資源大小的臨界值,當小於臨界值的時候,字體或者圖片文件會以base64的形式在html引用,不然則是以資源路徑的形式引用。以下:

// webpack.config.js // 圖片 loader { test: /\.(png|gif|jpe?g)$/, loader: 'url-loader', query: { /* * limit=10000 : 10kb * 圖片大小小於10kb 採用內聯的形式,不然輸出圖片 * */ limit: 10000, name: '/img/[name]-[hash:8].[ext]' } }, // 字體loader { test: /\.(eot|woff|woff2|ttf|svg)$/, loader: 'url-loader', query: { limit: 5000, name: '/font/[name]-[hash:8].[ext]' } },

資源文件的壓縮

js、css、html的壓縮是少不了的,webpack 自帶了壓縮插件,若是某些對象名稱不想被壓縮,能夠排除不想要壓縮的對象名稱。配置以下:

// webpack.config.js ... plugins:[ new webpack.optimize.UglifyJsPlugin({ mangle: { // 排除不想要壓縮的對象名稱 except: ['$super', '$', 'exports', 'require', 'module', '_'] }, compress: { warnings: false }, output: { comments: false, } }) ] ...

使用jquery、underscore

經過webpack編譯輸出後的項目中,雖然頁面已經引用了jquery、underscore,可是仍是沒法直接使用‘$’、‘_’對象,咱們能夠這樣:

var $ = require('jquery'); var _ = require('underscore');

可是這樣實在不方便,若是咱們就是要使用‘$’、‘_’對象直接操做,webpack 內置的插件能夠幫咱們解決。具體以下:

// webpack.config.js new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", "window.jQuery": "jquery", "_": "underscore", }), 

代碼分割,按需加載

在單頁面應用中,當咱們加載其餘的模板文件時,想要引用這個模板文件對應的js。若是咱們經過這種方式require(),那麼webpack會將這個模板文件對應的js也會和當前js打包成一個js。若是項目比較大,那麼js文件也將愈來愈大。咱們但願的是加載模板文件的時候動態的引用這個模板文件對應的js。那麼咱們能夠經過 require.ensure()的方式。

好比如今有兩個導航菜單:

<ul> <li><a href="#home">home</a></li> <li><a href="#hrm">HRM</a></li> </ul>

咱們給這兩個菜單綁定點擊事件,當點擊‘home’時引用對應的‘home.js’;當點擊‘HRM’時引用對應的‘hrm.js’,那麼大體能夠這樣:

function loadJs(jsPath) { var currentMod; if (jsPath === './home') { require.ensure([], function (require) { currentMod = require('./home'); }, 'home'); } else if (jsPath === './hrm') { require.ensure([], function (require) { currentMod = require('./hrm'); }, 'hrm'); } }

全局環境變量

有時咱們只有在開發過程當中,纔想輸出log日誌。能夠用如下webpack內置的插件解決:

// webpack.config.js ... plugins:[ new webpack.DefinePlugin({ // 全局debug標識 __DEV__: debug, }), ] ...

這時代碼中就能夠這麼寫了:

if (__DEV__) { console.log('debug 模式'); }

清空發佈目錄

發佈前清空發佈目錄是有必要的,咱們能夠經過‘clean-webpack-plugin’插件解決:

引入插件:

// webpack.config.js var CleanWebpackPlugin = require('clean-webpack-plugin');

配置plugins:

// webpack.config.js ... plugins:[ new CleanWebpackPlugin(['dist'], { root: '', // An absolute path for the root of webpack.config.js verbose: true,// Write logs to console. dry: false // Do not delete anything, good for testing. }), ] ...

熱更新結合後端服務

熱更新

熱更新能夠在你代碼改變的時候即時編譯輸出,不用每次都要從都從新編譯一遍,而且除了第一次編譯比較慢,後面的編譯都是增量編譯,速度很快。有了這個功能,咱們就不須要,每次都從頭編譯一次了。配置以下:

// webpack.config.js ... plugins: [ // Enable multi-pass compilation for enhanced performance // in larger projects. Good default. new webpack.HotModuleReplacementPlugin({ multiStep: true }), ], devServer: { // Enable history API fallback so HTML5 History API based // routing works. This is a good default that will come // in handy in more complicated setups. historyApiFallback: true, // Unlike the cli flag, this doesn't set // HotModuleReplacementPlugin! hot: true, inline: true, // Display only errors to reduce the amount of output. stats: 'errors-only', host: "localhost", // Defaults to `localhost` process.env.HOST port: "8080", // Defaults to 8080 process.env.PORT } ... 

這時咱們只要打開瀏覽器,輸入:localhost:8080/ 就能看到結果,而且在你修改某些源文件後,瀏覽器會自動刷新,就能看到webpack 即時編譯輸出的結果,而不須要從新編譯。

結合後端服務

咱們在使用webpack開發時不免要結合後端服務開發,好比咱們用webstorm 編譯器開發項目,須要調用java的服務,因爲有同源策略問題,這時咱們會收到相關報錯信息。這時咱們能夠經過代理的方式繞過同源策略。這裏我用nodejs 模擬一個後端服務,以下:

// ~/mockServer/server.js var http = require('http'); var content = '▍if you see that,It means you have get the correct data by backend server(mock data by nodejs server)!'; var srv = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'application/text'}); res.end(content); }); srv.listen(8888, function() { console.log('listening on localhost:8888'); });

接下來咱們須要這樣配置去調用這個nodejs 的服務。首先將熱更新配置的代碼修改成:

// webpack.config.js ... plugins: [ // Enable multi-pass compilation for enhanced performance // in larger projects. Good default. new webpack.HotModuleReplacementPlugin({ multiStep: true }), ], devServer: { // Enable history API fallback so HTML5 History API based // routing works. This is a good default that will come // in handy in more complicated setups. historyApiFallback: true, // Unlike the cli flag, this doesn't set // HotModuleReplacementPlugin! hot: true, inline: true, // Display only errors to reduce the amount of output. stats: 'errors-only', host: "localhost", // Defaults to `localhost` process.env.HOST port: "8080", // Defaults to 8080 process.env.PORT proxy: { '/devApi/*': { target: 'http://localhost:8888/', secure: true, /* * rewrite 的方式擴展性更強,不限制服務的名稱 * */ rewrite: function (req) { req.url = req.url.replace(/^\/devApi/, ''); } } } } ...

而後配置一個全局的環境變量,經過DefinePlugin

// webpack.config.js ... plugins: [ new webpack.DefinePlugin({ __DEVAPI__: devServer ? "/devApi/" : "''", }), ] ...

最後在調用服務的地方,只須要在調用地址前添加 __DEVAPI__全局環境變量便可,如:

$.ajax({
        url: __DEVAPI__ + 'http://localhost:8888/', data: {}, type: 'get', dataType: 'text', success: function (text) {} })

這樣在熱更新的模式下,當有__DEVAPI__ 的地方就會自動識別爲/devApi/,而這裏會經過代理處理幫你重寫掉,繞過同源策略。

自動打開瀏覽器

雖然以上的工做幾乎已經知足咱們對webpack的要求了,可是咱們還想懶一點,想在熱更新模式下,編譯完成後自動打開瀏覽器。那麼咱們能夠經過這個插件open-browser-webpack-plugin解決:

引用插件

// webpack.config.js var OpenBrowserPlugin = require('open-browser-webpack-plugin');

配置插件,這個配置要根據項目的具體狀況去配置:

// webpack.config.js ... plugins: [ new OpenBrowserPlugin({url: 'http://localhost:8080' + PATHS.publicPath + 'index.html'}) ] ...

總結

以上就是這篇文章的主要內容,但願經過這篇文章可以給你們帶來一些啓發。若是有以爲哪裏不對,或者不合理的地方,歡迎指出。其實webpack還有一個關於版本號的bug,不知道是否是有人解決了,若是有人已經解決了,還請分享。

相關文章
相關標籤/搜索