webpack构建静态多页应用项目框架

最近因为项目需求,需要使用webpack构建一个静态的多页应用,譬如主页网站之类。本文主要来解析一下按功能模块划分的项目结构,再重点分析webpack的配置文件。项目源码github地址

view engine使用jade, css 预编译使用stylus,这个影响不大。
首先,我们来看一下项目的基本结构

  • 项目基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- webpack-multiplepage
+ build
- source
- components #组件
- common
+ styles #公共样式
+ header #头部组件
+ footer
- layout #布局组件
index.js #layout入口文件,,可导入依赖,及写一些逻辑代码
layout.styl #layout样式
bg.png
layout.jade #页面布局jade,include了header.jade和footer.jade
- pages #页面
+ about #关于模块
+ help #帮助模块
- index #首页模块
index.jade #首页内容,extends layout.jade
index.js #首页入口文件,可导入依赖,及写一些逻辑代码
index.styl #首页样式
.eslintrc
.eslintignore
.gitignore
webpack.config.js #webpack配置文件
package.json
README.md

layout组件是所有页面公用的布局组件,各个页面入口以require('../../components/layout')的方式引入,各个页面jade模版继承于layout.jade

同时layout/index.js中require('../header')require('../footer')的方式引入header和footer模块(可以直接将header,footer有关代码写在layout中,不单独拎出)

项目上线时,以下是用webpack build之后的结果

打包结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- build
- assets #字体,图片资源
+ fonts
+ images
- common #公用文件,包含了第三方依赖和抽离出来的公共代码
common.****.js
common.****.css
- about #about模块
about.****.js
about.****.css
about.html
+ help
- index
index.****.js
index.****.css
index.html #index首页置于根目录,方便webpack监听

webpack.config.js文件解析

build过程可以通过webpack.config.js文件进行灵活的配置,下面详细解释该文件,请看注释。
有关webpack的用法和各个插件的用法可以查看官方文档插件列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// 依赖
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var autoprefixer = require('autoprefixer');
var args = require('yargs').argv;

// 命令行传入的环境参数
// 可通过--prod,--mock传入
var isProd = args.prod;
var isMock = args.mock;

var plugins = [
// Automatically loaded modules
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
}),
// create global constants which can be configured at compile time
new webpack.DefinePlugin({
__PROD__: isProd,
__MOCK__: isMock
}),
// 抽取公共模块
new webpack.optimize.CommonsChunkPlugin('common', isProd ? 'common/common.[hash].js' : 'common/common.js'),
// 对每个entry抽取css依赖,生成css文件
// 注意:ExtractTextPlugin需在config的plugin及loader中一起使用
new ExtractTextPlugin(isProd ? '[name]/[name].[contenthash].css' : '[name]/[name].css'),
// 热替换
new webpack.HotModuleReplacementPlugin()
];

// 生成html,并为html插入css,js等依赖
// 将首页的index.html 置于根目录
plugins.push(
new HtmlWebpackPlugin({
template: './source/pages/index/index.jade',
filename: 'index.html',
chunks: ['common', 'index']
}),
new HtmlWebpackPlugin({
template: './source/pages/help/help.jade',
filename: 'help/index.html',
chunks: ['common', 'help']
}),
new HtmlWebpackPlugin({
template: './source/pages/about/about.jade',
filename: 'about/index.html',
chunks: ['common', 'about']
})
);


var base = './';
// 多个入口文件集合
var entryJs = {
index: [base + '/source/pages/index/index.js'],
help: [base + '/source/pages/help/help.js'],
about: [base + '/source/pages/about/about.js']
};
// 通用第三方模块 ,最后和抽离的公共模块合并
entryJs['common'] = [
// 3rd dependencies
'normalize.css/normalize.css',
'bootstrap/dist/js/bootstrap.min',
'bootstrap/dist/css/bootstrap.min.css',
'font-awesome/css/font-awesome.min.css'
];


if (isProd) {
// 生产环境优化
plugins.push(
new webpack.NoErrorsPlugin(),
// Search for equal or similar files and deduplicate them in the output.
new webpack.optimize.DedupePlugin(),
// Minimize all JavaScript output of chunks
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
mangle: false
}),
new webpack.optimize.OccurenceOrderPlugin()
);
}

module.exports = {
entry: entryJs,
// 打包文件输出配置
output: {
path: base + 'build',
// e.g index/index.****.js
filename: isProd ? '[name]/[name].[hash].js' : '[name]/[name].js',
chunkFilename: isProd ? '[name]/[name].[hash].chunk.js' : '[name]/[name].chunk.js'
},
module: {
preLoaders: [{
test: /\.js$/,
loader: 'eslint',
exclude: /node_modules/
}],
loaders: [{
test: /\.html$/,
loader: 'html'
}, {
test: /\.jade$/,
loader: 'jade'
}, {
test: /\.styl$/,
loader: ExtractTextPlugin.extract('vue-style', 'css?sourceMap!postcss!stylus', {
// 指定build根目录对css打包存储位置的路径,默认css直接放在根目录下
// 各个css均被打包到build/*/文件夹中,所以路径为'../'
publicPath: '../'
})
}, {
test: /\.css$/,
loader: ExtractTextPlugin.extract('vue-style', 'css?sourceMap', {
publicPath: '../'
})
}, {
test: /\.(woff|woff2|ttf|eot|svg)(\?]?.*)?$/,
loader: 'file?name=assets/fonts/[name].[ext]?[hash]'
}, {
test: /\.(png|jpg)$/,
loader: 'url?limit=8192&name=assets/images/[name].[hash].[ext]'
}]
},
plugins: plugins,
debug: !isProd,
// sourceMap的几种模式差别解析,可参考https://segmentfault.com/a/1190000004280859这篇文章
devtool: isProd ? false : 'eval-cheap-module-source-map',
// 可访问localhost:8080查看效果
devServer: {
contentBase: base + 'build',
historyApiFallback: true,
inline: true,
stats: {
modules: false,
cached: false,
colors: true,
chunk: false
},
host: '0.0.0.0',
port: 8080
},
// 使用postcss自动给css样式加前缀
postcss: function() {
return [autoprefixer];
}
};

配置优化?

以上的webpack配置有一个小小的问题,就是每次我添加一个新的页面模块时,比如product页面,需要手动在webpack.config文件里添加一个entry入口和HtmlWebpackPlugin。所以可以尝试用以下的的方法解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// npm install --save-dev glob
var getFiles = function(filepath) {
var files = glob.sync(filepath);
var entries = {};
files.forEach(function(item){
var pathname = path.basename(item, path.extname(item))
entries[pathname] = item;
});
return entries;
}

var pages = getFiles('./source/pages/*/*.jade');
// 生成html,插入依赖
Object.keys(pages).forEach(function(pageName){
plugins.push(
new HtmlWebpackPlugin({
template: './source/pages/'+ pageName+ '/' pageName + '.jade',
filename: pageName +'.html',
chunks: [ 'vendor', pageName],
})
);
});

var entryJs = getFiles('./source/pages/*/*.js');

遵循目录结构的话,这样写应该不会有什么问题。

demo效果

仅仅搭了一个架子,尚未进行项目编写,后续可能遇到一些实际问题,再做补充。
最后放一张运行结果图
demo运行图

参考项目:

文章目录
  1. 1. 项目基本结构
  2. 2. 打包结果
  3. 3. webpack.config.js文件解析
    1. 3.1. 配置优化?
  4. 4. demo效果
,