Skip to content

Commit f1d41e4

Browse files
author
Guillaume Chau
committed
feat: critical CSS in production
1 parent 03225cf commit f1d41e4

File tree

5 files changed

+72
-40
lines changed

5 files changed

+72
-40
lines changed

lib/loaders/css-context.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const path = require('path')
2+
const loaderUtils = require('loader-utils')
3+
const hash = require('hash-sum')
4+
const qs = require('querystring')
5+
6+
module.exports = function () {}
7+
8+
module.exports.pitch = function (remainingRequest) {
9+
const isServer = this.target === 'node'
10+
const isProduction = this.minimize || process.env.NODE_ENV === 'production'
11+
12+
const addStylesServerPath = loaderUtils.stringifyRequest(this, '!vue-style-loader/lib/addStylesServer.js')
13+
14+
const request = loaderUtils.stringifyRequest(this, '!!' + remainingRequest)
15+
const relPath = path.relative(__dirname, this.resourcePath).replace(/\\/g, '/')
16+
const id = JSON.stringify(hash(request + relPath))
17+
const options = loaderUtils.getOptions(this) || {}
18+
19+
// direct css import from js --> direct, or manually call `styles.__inject__(ssrContext)` with `manualInject` option
20+
// css import from vue file --> component lifecycle linked
21+
// style embedded in vue file --> component lifecycle linked
22+
const isVue = (
23+
/"vue":true/.test(remainingRequest) ||
24+
options.manualInject ||
25+
qs.parse(this.resourceQuery.slice(1)).vue != null
26+
)
27+
28+
if (isServer && isProduction) {
29+
const shared = [
30+
'// load the styles',
31+
`var content = require(${request});`,
32+
// content list format is [id, css, media, sourceMap]
33+
`if(typeof content === 'string') content = [[module.id, content, '']];`,
34+
'module.exports = content;',
35+
'if(content.locals) module.exports = content.locals;',
36+
]
37+
// on the server: attach to Vue SSR context
38+
if (isVue) {
39+
// inside *.vue file: expose a function so it can be called in
40+
// component's lifecycle hooks
41+
return shared.concat([
42+
'// add CSS to SSR context',
43+
'var add = require(' + addStylesServerPath + ').default',
44+
'module.exports.__inject__ = function (context) {',
45+
' add(' + id + ', content, ' + isProduction + ', context)',
46+
'};',
47+
]).join('\n')
48+
} else {
49+
// normal import
50+
return shared.concat([
51+
'require(' + addStylesServerPath + ').default(' + id + ', content, ' + isProduction + ');',
52+
`console.log('addStylesServerPath', '${id}', content, require(${addStylesServerPath}).default, __VUE_SSR_CONTEXT__);`,
53+
]).join('\n')
54+
}
55+
}
56+
return `module.exports = require(${remainingRequest})`
57+
}

lib/plugins/ServerMiniCssExtractPlugin.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

lib/webpack.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const WebpackBar = require('webpackbar')
77

88
const config = require('./config')
99
const HtmlFilterPlugin = require('./plugins/HtmlFilterPlugin')
10-
const ServerMiniCssExtractPlugin = require('./plugins/ServerMiniCssExtractPlugin')
10+
const CssContextLoader = require.resolve('./loaders/css-context')
1111

1212
exports.getWebpackConfig = ({ target }) => {
1313
const service = config.service
@@ -26,19 +26,20 @@ exports.getWebpackConfig = ({ target }) => {
2626
if (!isClient) {
2727
webpackConfig.plugins.delete('friendly-errors')
2828

29-
// Fix mini-css-extract-plugin error on server
29+
const isExtracting = webpackConfig.plugins.has('extract-css')
30+
if (isExtracting) {
31+
// Remove extract
3032
const langs = ['css', 'postcss', 'scss', 'sass', 'less', 'stylus']
3133
const types = ['vue-modules', 'vue', 'normal-modules', 'normal']
3234
for (const lang of langs) {
3335
for (const type of types) {
3436
const rule = webpackConfig.module.rule(lang).oneOf(type)
35-
if (rule.uses.has('extract-css-loader')) {
36-
rule.use('extract-css-loader').loader(ServerMiniCssExtractPlugin.loader)
37+
rule.uses.delete('extract-css-loader')
38+
// Critical CSS
39+
rule.use('css-context').loader(CssContextLoader).before('css-loader')
3740
}
3841
}
39-
}
40-
if (webpackConfig.plugins.has('extract-css')) {
41-
webpackConfig.plugin('extract-css').use(ServerMiniCssExtractPlugin)
42+
webpackConfig.plugins.delete('extract-css')
4243
}
4344
}
4445

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@
3333
"cross-env": "^5.2.0",
3434
"express": "^4.16.3",
3535
"fs-extra": "^7.0.0",
36+
"hash-sum": "^1.0.2",
37+
"loader-utils": "^1.1.0",
3638
"lru-cache": "^4.1.3",
3739
"memory-fs": "^0.4.1",
38-
"mini-css-extract-plugin": "^0.4.2",
3940
"portfinder": "^1.0.17",
41+
"querystring": "^0.2.0",
4042
"rimraf": "^2.6.2",
4143
"serve-favicon": "^2.5.0",
4244
"webpack-dev-middleware": "^3.2.0",

yarn.lock

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,10 @@ has@^1.0.1:
768768
dependencies:
769769
function-bind "^1.1.1"
770770

771+
hash-sum@^1.0.2:
772+
version "1.0.2"
773+
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
774+
771775
hosted-git-info@^2.1.4:
772776
version "2.7.1"
773777
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
@@ -1045,14 +1049,6 @@ mimic-fn@^1.0.0:
10451049
version "1.2.0"
10461050
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
10471051

1048-
mini-css-extract-plugin@^0.4.2:
1049-
version "0.4.2"
1050-
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.2.tgz#b3ecc0d6b1bbe5ff14add42b946a7b200cf78651"
1051-
dependencies:
1052-
loader-utils "^1.1.0"
1053-
schema-utils "^1.0.0"
1054-
webpack-sources "^1.1.0"
1055-
10561052
minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
10571053
version "3.0.4"
10581054
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -1473,14 +1469,6 @@ [email protected]:
14731469
dependencies:
14741470
is-fullwidth-code-point "^2.0.0"
14751471

1476-
source-list-map@^2.0.0:
1477-
version "2.0.0"
1478-
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
1479-
1480-
source-map@~0.6.1:
1481-
version "0.6.1"
1482-
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
1483-
14841472
spdx-correct@^3.0.0:
14851473
version "3.0.0"
14861474
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82"
@@ -1700,13 +1688,6 @@ webpack-node-externals@^1.7.2:
17001688
version "1.7.2"
17011689
resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz#6e1ee79ac67c070402ba700ef033a9b8d52ac4e3"
17021690

1703-
webpack-sources@^1.1.0:
1704-
version "1.2.0"
1705-
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.2.0.tgz#18181e0d013fce096faf6f8e6d41eeffffdceac2"
1706-
dependencies:
1707-
source-list-map "^2.0.0"
1708-
source-map "~0.6.1"
1709-
17101691
webpackbar@^2.6.3:
17111692
version "2.6.3"
17121693
resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-2.6.3.tgz#4f2d0078375acfe95c0e55227771a2ed98ecc5c9"

0 commit comments

Comments
 (0)