基于webpack的前端工程化開發之多頁站點篇(二)

Sta2185 8年前發布 | 56K 次閱讀 HTML 前端技術 webpack

來自: https://segmentfault.com/a/1190000004516832

這篇,我們要解決上篇留下的兩個問題:

  • webpack如何自動發現entry文件及進行相應的模板配置

  • 如何直接處理后端模板的樣式、腳本自動引入問題

目錄結構

以express項目為例,使用express-generator構建一個初始項目,然后再添加需要的目錄,最終的目錄架構如下:

- website

- bin                    #express項目啟動文件
- lib                    #express項目開發所需的庫
+ routes                #express項目路由
- src                    #前端源碼開發目錄
    - styles            #css目錄,按照頁面(模塊)、通用、第三方三個級別進行組織
        + page
        + common
        + lib
    + imgs                #圖片資源
    - scripts            #JS腳本,按照page、components進行組織
        + page
        + components
    + views                #HTML模板
- public                #webpack編譯打包輸出目錄的靜態文件,express工程的靜態目錄
    + styles                
    + scripts
    + imgs
+ views                    #webpack編譯輸出的模板靜態文件,express工程的視圖模板 
+ node_modules            #所使用的nodejs模塊
package.json            #項目配置
webpack.config.js        #webpack配置
README.md                #項目說明</pre> 

你同樣可以根據個人喜好自由設計目錄結構。完整的源碼示例前往 https://github.com/vhtml/webpack-MultiPage

安裝開發依賴

package.json里最終的聲明依賴如下:

"devDependencies": {
    "css-loader": "^0.23.1",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.8.5",
    "glob": "^7.0.0",
    "html-loader": "^0.4.3",
    "html-webpack-plugin": "^2.9.0",
    "jquery": "^1.12.0",
    "less": "^2.6.0",
    "less-loader": "^2.2.2",
    "style-loader": "^0.13.0",
    "url-loader": "^0.5.7",
    "webpack": "^1.12.13",
    "webpack-dev-server": "^1.14.1"
}

可以看出,比上篇多了一個glob依賴,它是一個根據模式匹配獲取文件列表的node模塊。有關glob的詳細用法可以在這里看到—— https://github.com/isaacs/node-glob 。利用glob模塊可以很方便的獲取src/scripts/page路徑下的所有js入口文件。同理,可以實現自動的進行與入口文件相對應的模板配置。

webpack配置

最終的webpack配置如下:

var path = require('path');
var glob = require('glob');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;

const debug = process.env.NODE_ENV !== 'production';

var entries = getEntry('src/scripts/page/*/.js', 'src/scripts/page/'); var chunks = Object.keys(entries);

var config = { entry: entries, output: { path: path.join(__dirname, 'public'), publicPath: '/static/', filename: 'scripts/[name].js', chunkFilename: 'scripts/[id].chunk.js' }, module: { loaders: [ //加載器 { test: /.css$/, loader: ExtractTextPlugin.extract('style', 'css') }, { test: /.less$/, loader: ExtractTextPlugin.extract('css!less') }, { test: /.html$/, loader: "html" }, { test: /.(woff|woff2|ttf|eot|svg)(\?v=[0-9].[0-9].[0-9])?$/, loader: 'file-loader?name=fonts/[name].[ext]' }, { test: /.(png|jpe?g|gif)$/, loader: 'url-loader?limit=8192&name=imgs/[name]-[hash].[ext]' } ] }, plugins: [ new webpack.ProvidePlugin({ //加載jq $: 'jquery' }), new CommonsChunkPlugin({ name: 'vendors', // 將公共模塊提取,生成名為vendors的chunk chunks: chunks, minChunks: chunks.length // 提取所有entry共同依賴的模塊 }), new ExtractTextPlugin('styles/[name].css'), //單獨使用link標簽加載css并設置路徑,相對于output配置中的publickPath debug ? function() {} : new UglifyJsPlugin({ //壓縮代碼 compress: { warnings: false }, except: ['$super', '$', 'exports', 'require'] //排除關鍵字 }), new webpack.HotModuleReplacementPlugin() //熱加載 ], devServer: { publicPath:'http://localhost:8080/static/', proxy: { "*": "http://localhost:54999" }, inline: true, hot: true } };

var pages = Object.keys(getEntry('src/views/*/.html', 'src/views/')); pages.forEach(function(pathname) { var conf = { filename: '../views/' + pathname + '.html', //生成的html存放路徑,相對于path template: 'src/views/' + pathname + '.html', //html模板路徑 inject: false, //js插入的位置,true/'head'/'body'/false minify: { //壓縮HTML文件 removeComments: true, //移除HTML中的注釋 collapseWhitespace: false //刪除空白符與換行符 } }; if (pathname in config.entry) { conf.inject = 'body'; conf.chunks = ['vendors', pathname]; conf.hash = true; } config.plugins.push(new HtmlWebpackPlugin(conf)); });

module.exports = config;

function getEntry(globPath, pathDir) { var files = glob.sync(globPath); var entries = {}, entry, dirname, basename, pathname, extname;

for (var i = 0; i < files.length; i++) {
    entry = files[i];
    dirname = path.dirname(entry);
    extname = path.extname(entry);
    basename = path.basename(entry, extname);
    pathname = path.join(dirname, basename);
    pathname = pathDir ? pathname.replace(new RegExp('^' + pathDir), '') : pathname;
    entries[pathname] = './' + entry;
}
return entries;

}</pre>

探索之路

這里還要說說如何直接處理后端模板的問題。一開始本菜也是對這個問題進行了苦苦的探索,覺得可能真的實現不了,一度要放棄,并打算采用先純靜態打包再改寫成后端模板的方式(因為貌似還沒有這樣的loader可以很智能的處理模板include的問題以及在非html模板中自動引入css和js)。但是這樣做真的很蛋疼啊有木有!明明是一件事為什么要拆成兩件事去做呢?!

如果你也進行過這樣一番探索,你可能接觸過像jade-loader、ejs-loader、ejs-compiled-loader等這樣的webpack loader。無奈它們統統都不是我要找的,它們只是編譯了模板而沒有保留模板原有的生態,也不能自動地引入css和js。我也曾試過自己寫loader將ejs模板先轉成html模板(只處理include標簽,其余原樣保留)再用html-loader去處理,但又破壞了模板的可復用性,失去了靈活性。

好吧,其實只是想原樣輸出src/views中的模板,然后像上篇中那樣自動引入css和js,僅此而已。沒想到差一點鉆了死胡同,想得過于復雜了。

我們應該先知道一個事實,html-webpack-plugin插件實現自動引入css和js的原理,是在模板中對應的成對head和body標簽中進行解析插入。如果沒有head和body標簽,它會分別在模板頭和尾生成這兩個標簽并插入link和script標簽來引入css和js。而至于你的模板里寫了什么,它是不會關心的。明白了這個原理,要完成“大業”就為期不遠了。我們應該先改一改寫模板的方式,模板結構一定要是類html的,不能是jade這種(還好我并不喜歡用jade)。以artTemplate模板為例,如下:

<!DOCTYPE html>
<html>
<head>
    {{include './common/meta'}}
</head>
<body>
    {{include './common/header'}}
    <div class="g-bd">
        {{include './common/_content'}}
    </div>
    {{include './common/footer'}}
</body>
</html>

是的,沒錯,只要保留完整的head、body結構即可。然后根據上述的webpack配置,將與入口js對應的模板插入link和script標簽并輸出到./views目錄中,其余模板原樣輸出到./views目錄或相應的子目錄下即可。

到此,“大業”完成。

假如你有更好的解決方案,歡迎一起分享。

</div>

 本文由用戶 Sta2185 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!