大多数状况下,咱们使用 webpack
来打包单页应用程序,这个时候只须要配置一个入口,一个模板文件,但也不满是如此,有时候也会碰到多页面的项目,并且以个人经验来看,这种状况出现的频率还不低,例如项目比较大,没法进行全局的把握,或者项目须要屡次的更新迭代等,都适合作成多页面程序,这就涉及到了 webpack
的多页面文件的打包配置问题。css
单页应用程序和多页应用程序的 webpack
配置文件其实绝大部分都仍是相同的,只不过多页的配置须要在单页配置的基础上顾及到多个页面罢了,loader
、output
、plugins
这些基本都不须要改动,须要改动的通常都是入口文件 entry
,若是你用到了 抽离css
样式的插件 extract-text-webpack-plugin
、自动模板插件 html-webpack-plugin
的话,那么还须要对这两个插件进行额外的改写,大多数状况下,咱们也都只须要改动这三个地方,因此本文就只简单说下这三个位置,若是在实际的项目中还有其余的地方须要改动,参照这三个位置便可。html
示例的文件目录以下:node
单页应用程序的入口配置通常以下所示:webpack
entry: resolve(__dirname, "src/home/index.js")
复制代码
这个配置就是指定 webpack
从 /src/home/index.js
这个文件开始进入,进行一系列的打包编译过程。git
若是是多页应用程序,则须要多个入口文件,例如:github
entry: {
home: resolve(__dirname, "src/home/index.js"),
about: resolve(__dirname, "src/about/index.js")
}
复制代码
这样,整个项目就有了两个入口 home
和 about
web
extract-text-webpack-plugin
插件主要是为了抽离css
样式,防止将样式打包在 js
中引发页面样式加载错乱或者 js
脚本体积过大等状况,单页程序中,通常这样使用此插件:json
plugins: [
new ExtractTextPlugin('style.[contenthash].css')
]
复制代码
而到了多页程序,由于存在多个入口文件以及对应的多个页面,每一个页面都有本身的 css
样式,因此须要为每一个页面各自配置一下:windows
plugins: [
new ExtractTextPlugin('home/[name].[contenthash].css'),
new ExtractTextPlugin('about/[name].[contenthash].css')
]
复制代码
除此以外还须要注意一点,每一个页面也只须要本身的 css
样式,理论上把别的页面 css
样式文件也打包到本身的页面中固然也是能够的,但显然是不合理的,这只会增长冗余代码,还可能会致使不可预测的样式覆盖等问题,因此须要对下面这种 loader
配置进行修改:sass
{
test: /\.css$/,
loader: 'style!css!autoprefixer'
},
{
test: /\.scss$/,
loaders: [
'style',
'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
'sass',
'autoprefixer'
]
},
复制代码
上面的配置会把全部编译出来的 css
文件打包到同一个文件中,咱们要作的就是把这些 css
分离开,每一个页面都有各自单独的 css
样式文件:
// 为每一个页面定义一个 ExtractTextPlugin
const homeExtractCss = new ExtractTextPlugin('home/[name].[contenthash].css')
const aboutExtractCss = new ExtractTextPlugin('about/[name].[contenthash].css')
// ...
module: {
rules: [
// 每一个页面的 ExtractTextPlugin 只处理这个页面的样式文件
{
test: /src(\\|\/)home(\\|\/)css(\\|\/).*\.(css|scss)$/,
use: homePageExtractCss.extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader', 'sass-loader']
})
},
{
test: /src(\\|\/)about(\\|\/)css(\\|\/).*\.(css|scss)$/,
use: salePersonalCenterExtractCss.extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader', 'sass-loader']
})
}
]
}
// ...
// 每一个页面都有各自的 ExtractTextPlugin,因此须要都声明一遍
plugins: [
homeExtractCss,
aboutExtractCss
]
复制代码
html-webpack-plugin
插件的使用,在单页应用程序和多页应用程序中的 webpack
配置没什么区别
new HtmlWebpackPlugin({
filename: 'home/home.html',
template: 'src/home/html/index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true
}
})
new HtmlWebpackPlugin({
filename: 'about/about.html',
template: 'src/about/html/index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true
}
})
复制代码
有几个页面,就对每一个页面进行上述配置便可。
上述的配置代码已经能够知足多页面开发需求了,可是有一点彷佛有些遗憾,那就是每增长一个页面,就须要更新一遍 entry
、extract-text-webpack-plugin
、HtmlWebpackPlugin
的配置,虽然只是几行代码的问题,并且基本上都是复制粘贴没什么难度,但毕竟代码再少也须要过问,而且须要改的地方比较多,仓促之下可能还会遗漏,要是能一劳永逸,写一遍代码,不管之后增删页面都不须要过问就行了。
稍微观察下这个目录就能够发现,这个目录结构实际上是颇有规律的:
每一个页面都是 src/
目录下的一个文件夹,这个文件夹中有两个子目录,分别存放这个页面的模板 html
,样式文件 css
,还有一个入口文件 index.js
既然有规则,那么确定是能够进行程序编码的,若是按照这种规则,每一个页面都是 ./src
下的一个目录,目录名即为页面名,而且这个目录中的结构也都是相同的,那么能够经过一个通用方法来获取全部的页面名称(例如 home
、about
),这个通用方法的一个示例以下:
function getEntry () {
let globPath = 'src/**/html/*.html'
// (\/|\\\\) 这种写法是为了兼容 windows和 mac系统目录路径的不一样写法
let pathDir = 'src(\/|\\\\)(.*?)(\/|\\\\)html'
let files = glob.sync(globPath)
let dirname, entries = []
for (let i = 0; i < files.length; i++) {
dirname = path.dirname(files[i])
entries.push(dirname.replace(new RegExp('^' + pathDir), '$2'))
}
return entries
}
复制代码
借助 glob这个库,遍历 .src/
目录下具备这种规律 src/**/html/*.html
的子目录,经过正则匹配出这个子目录的名称
获取到了全部的页面名称,下面就好办了。
// entry: resolve(__dirname, "src/home/index.js")
// 改成
entry: addEntry()
//...
function addEntry () {
let entryObj = {}
getEntry().forEach(item => {
entryObj[item] = resolve(__dirname, 'src', item, 'index.js')
})
return entryObj
}
复制代码
// plugins: [
// new ExtractTextPlugin('home/[name].[contenthash].css'),
// new ExtractTextPlugin('about/[name].[contenthash].css')
//]
// 改成
const pageExtractCssArray = []
getEntry().forEach(item => {
pageExtractCssArray.push(new ExtractTextPlugin(item + '/[name].[contenthash].css'))
})
// ...
plugins: [...pageExtractCssArray]
复制代码
module.rules
样式相关的两个loaders
删掉,改成动态添加:
getEntry().forEach((item, i) => {
webpackconfig.module.rules.push({
test: new RegExp('src' + '(\\\\|\/)' + item + '(\\\\|\/)' + 'css' + '(\\\\|\/)' + '.*\.(css|scss)$'),
use: pageExtractCssArray[i].extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader', 'sass-loader']
})
})
})
// ...
module.exports = webpackconfig
复制代码
plugins
中无需手动初始化 html-webpack-plugin
,改成动态添加:
getEntry().forEach(pathname => {
let conf = {
filename: path.join(pathname, pathname) + '.html',
template: path.join(__dirname, 'src', pathname, 'html', 'index.html')
}
webpackconfig.plugins.push(new HtmlWebpackPlugin(conf))
})
// ...
module.exports = webpackconfig
复制代码
完成了上述修改后,之后不管是在项目中添加页面仍是删除页面,都无需再对 webpack
配置进行手动修改了,虽然开始时开起来彷佛这种动态的自动配置代码比较多,并且稍微复杂一点,可是从长期来看,绝对是一劳永逸的好作法。
另外,若是你的项目目录结构和我示例的目录结构不同,那么就须要你根据本身的目录结构对代码进行少量的修改,但总体解决问题的方法是不变的,一个易于维护的项目,目录结构都该是有律可循的。