Skip to content

Commit d293194

Browse files
committed
fix: css extraction
1 parent ce86da7 commit d293194

File tree

5 files changed

+157
-9
lines changed

5 files changed

+157
-9
lines changed

Diff for: lib/webpack/RemoveEmptyChunkPlugin.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
2+
module.exports = class Plugin {
3+
apply (compiler) {
4+
compiler.hooks.emit.tap('vuepress-remove-empty-chunk', compilation => {
5+
Object.keys(compilation.assets).forEach(name => {
6+
if (/_assets\/js\/styles\.\w{8}\.js$/.test(name)) {
7+
delete compilation.assets[name]
8+
}
9+
})
10+
})
11+
}
12+
}

Diff for: lib/webpack/baseConfig.js

+38-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module.exports = function createBaseConfig ({
77
publicPath,
88
themePath,
99
notFoundPath
10-
}, { debug } = {}) {
10+
}, { debug } = {}, isServer) {
1111
const markdown = require('../markdown')(siteConfig)
1212
const Config = require('webpack-chain')
1313
const { VueLoaderPlugin } = require('vue-loader')
@@ -142,10 +142,12 @@ module.exports = function createBaseConfig ({
142142
applyLoaders(normalRule, false)
143143

144144
function applyLoaders (rule, modules) {
145-
if (isProd) {
146-
rule.use('extract-css-loader').loader(CSSExtractPlugin.loader)
147-
} else {
148-
rule.use('vue-style-loader').loader('vue-style-loader')
145+
if (!isServer) {
146+
if (isProd) {
147+
rule.use('extract-css-loader').loader(CSSExtractPlugin.loader)
148+
} else {
149+
rule.use('vue-style-loader').loader('vue-style-loader')
150+
}
149151
}
150152

151153
rule.use('css-loader').loader('css-loader').options({
@@ -174,10 +176,39 @@ module.exports = function createBaseConfig ({
174176
.plugin('vue-loader')
175177
.use(VueLoaderPlugin)
176178

177-
if (isProd) {
179+
if (isProd && !isServer) {
178180
config
179181
.plugin('extract-css')
180-
.use(CSSExtractPlugin, [{ filename: '_assets/css/styles.[hash:8].css' }])
182+
.use(CSSExtractPlugin, [{
183+
filename: '_assets/css/styles.[chunkhash:8].css'
184+
}])
185+
186+
// ensure all css are extracted together.
187+
// since most of the CSS will be from the theme and very little
188+
// CSS will be from async chunks
189+
config
190+
.set('optimization', {
191+
splitChunks: {
192+
cacheGroups: {
193+
chunks: 'all',
194+
styles: {
195+
name: 'styles',
196+
// necessary for extraction to include md files as well
197+
test: m => /css-extract/.test(m.type),
198+
chunks: 'all',
199+
enforce: true
200+
}
201+
}
202+
}
203+
})
204+
205+
// enforcing all styles extraction leaves an empty styles chunk.
206+
// prevent it from being emitted.
207+
// this is a bug in mini-css-extract-plugin
208+
// https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
209+
config
210+
.plugin('remove-empty-chunk')
211+
.use(require('./RemoveEmptyChunkPlugin'))
181212
}
182213

183214
return config

Diff for: lib/webpack/clientConfig.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ module.exports = function createClientConfig (options, cliOptions) {
2929

3030
// generate client manifest only during build
3131
if (process.env.NODE_ENV === 'production') {
32-
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
32+
// TODO this is a temp build of vue-server-renderer/client-plugin.
33+
// Switch back after problems are resolved.
34+
// Fixes two things:
35+
// 1. Include CSS in preload files
36+
// 2. filter out useless styles.xxxxx.js chunk from mini-css-extract-plugin
37+
// https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
38+
const VueSSRClientPlugin = require('./clientPlugin')
3339
config
3440
.plugin('ssr-client')
3541
.use(VueSSRClientPlugin, [{

Diff for: lib/webpack/clientPlugin.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Temporarily copied from a dev build
2+
3+
'use strict'
4+
5+
/* */
6+
7+
var isJS = function (file) { return /\.js(\?[^.]+)?$/.test(file) }
8+
9+
var isCSS = function (file) { return /\.css(\?[^.]+)?$/.test(file) }
10+
11+
var onEmit = function (compiler, name, hook) {
12+
if (compiler.hooks) {
13+
// Webpack >= 4.0.0
14+
compiler.hooks.emit.tapAsync(name, hook)
15+
} else {
16+
// Webpack < 4.0.0
17+
compiler.plugin('emit', hook)
18+
}
19+
}
20+
21+
var hash = require('hash-sum')
22+
var uniq = require('lodash.uniq')
23+
var VueSSRClientPlugin = function VueSSRClientPlugin (options) {
24+
if (options === void 0) options = {}
25+
26+
this.options = Object.assign({
27+
filename: 'vue-ssr-client-manifest.json'
28+
}, options)
29+
}
30+
31+
VueSSRClientPlugin.prototype.apply = function apply (compiler) {
32+
var this$1 = this
33+
34+
onEmit(compiler, 'vue-client-plugin', function (compilation, cb) {
35+
var stats = compilation.getStats().toJson()
36+
37+
var allFiles = uniq(stats.assets
38+
.map(function (a) { return a.name }))
39+
.filter(file => {
40+
return !/styles\.\w{8}\.js$/.test(file)
41+
})
42+
43+
var initialFiles = uniq(Object.keys(stats.entrypoints)
44+
.map(function (name) { return stats.entrypoints[name].assets })
45+
.reduce(function (assets, all) { return all.concat(assets) }, [])
46+
.filter(function (file) { return isJS(file) || isCSS(file) }))
47+
.filter(file => {
48+
return !/styles\.\w{8}\.js$/.test(file)
49+
})
50+
51+
var asyncFiles = allFiles
52+
.filter(function (file) { return isJS(file) || isCSS(file) })
53+
.filter(function (file) { return initialFiles.indexOf(file) < 0 })
54+
55+
var manifest = {
56+
publicPath: stats.publicPath,
57+
all: allFiles,
58+
initial: initialFiles,
59+
async: asyncFiles,
60+
modules: { /* [identifier: string]: Array<index: number> */ }
61+
}
62+
63+
var assetModules = stats.modules.filter(function (m) { return m.assets.length })
64+
var fileToIndex = function (file) { return manifest.all.indexOf(file) }
65+
stats.modules.forEach(function (m) {
66+
// ignore modules duplicated in multiple chunks
67+
if (m.chunks.length === 1) {
68+
var cid = m.chunks[0]
69+
var chunk = stats.chunks.find(function (c) { return c.id === cid })
70+
if (!chunk || !chunk.files) {
71+
return
72+
}
73+
var id = m.identifier.replace(/\s\w+$/, '') // remove appended hash
74+
var files = manifest.modules[hash(id)] = chunk.files.map(fileToIndex)
75+
// find all asset modules associated with the same chunk
76+
assetModules.forEach(function (m) {
77+
if (m.chunks.some(function (id) { return id === cid })) {
78+
files.push.apply(files, m.assets.map(fileToIndex))
79+
}
80+
})
81+
}
82+
})
83+
84+
// const debug = (file, obj) => {
85+
// require('fs').writeFileSync(__dirname + '/' + file, JSON.stringify(obj, null, 2))
86+
// }
87+
// debug('stats.json', stats)
88+
// debug('client-manifest.json', manifest)
89+
90+
var json = JSON.stringify(manifest, null, 2)
91+
compilation.assets[this$1.options.filename] = {
92+
source: function () { return json },
93+
size: function () { return json.length }
94+
}
95+
cb()
96+
})
97+
}
98+
99+
module.exports = VueSSRClientPlugin

Diff for: lib/webpack/serverConfig.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module.exports = function createServerConfig (options, cliOptions) {
66
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
77
const CopyPlugin = require('copy-webpack-plugin')
88

9-
const config = createBaseConfig(options, cliOptions)
9+
const config = createBaseConfig(options, cliOptions, true /* isServer */)
1010
const { sourceDir, outDir } = options
1111

1212
config

0 commit comments

Comments
 (0)