Skip to content

Commit 98afd07

Browse files
committed
feat: inject styles under shadow root in web component mode
1 parent 6db7735 commit 98afd07

File tree

8 files changed

+70
-78
lines changed

8 files changed

+70
-78
lines changed

packages/@vue/cli-service/lib/commands/build/entry-web-component.js

+7-13
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ const name = process.env.CUSTOM_ELEMENT_NAME
1212
// - true: the instance is always kept alive
1313
const keepAlive = process.env.CUSTOM_ELEMENT_KEEP_ALIVE
1414

15-
// Whether to use Shadow DOM.
16-
// default: true
17-
const useShadowDOM = process.env.CUSTOM_ELEMENT_USE_SHADOW_DOM
18-
1915
const options = typeof Component === 'function'
2016
? Component.options
2117
: Component
@@ -30,6 +26,10 @@ const props = Array.isArray(options.props)
3026
: options.props || {}
3127
const propsList = Object.keys(props)
3228

29+
// CSS injection function exposed by vue-loader & vue-style-loader
30+
const styleInjectors = window[options.__shadowInjectId]
31+
const injectStyle = root => styleInjectors.forEach(inject => inject(root))
32+
3333
// TODO use ES5 syntax
3434
class CustomElement extends HTMLElement {
3535
static get observedAttributes () {
@@ -49,21 +49,15 @@ class CustomElement extends HTMLElement {
4949
})
5050

5151
this._attached = false
52-
if (useShadowDOM) {
53-
this._shadowRoot = this.attachShadow({ mode: 'open' })
54-
}
52+
this._shadowRoot = this.attachShadow({ mode: 'open' })
53+
injectStyle(this._shadowRoot)
5554
}
5655

5756
connectedCallback () {
5857
this._attached = true
5958
if (!this._wrapper._isMounted) {
6059
this._wrapper.$mount()
61-
const el = this._wrapper.$el
62-
if (useShadowDOM) {
63-
this._shadowRoot.appendChild(el)
64-
} else {
65-
this.appendChild(el)
66-
}
60+
this._shadowRoot.appendChild(this._wrapper.$el)
6761
}
6862
this._wrapper._data._active = true
6963
}

packages/@vue/cli-service/lib/commands/build/index.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ const defaults = {
22
mode: 'production',
33
target: 'app',
44
entry: 'src/App.vue',
5-
keepAlive: false,
6-
shadow: true
5+
keepAlive: false
76
}
87

98
module.exports = (api, options) => {
@@ -12,16 +11,19 @@ module.exports = (api, options) => {
1211
usage: 'vue-cli-service build [options]',
1312
options: {
1413
'--mode': `specify env mode (default: ${defaults.mode})`,
14+
'--dest': `specify output directory (default: ${options.outputDir})`,
1515
'--target': `app | lib | web-component (default: ${defaults.target})`,
1616
'--entry': `entry for lib or web-component (default: ${defaults.entry})`,
1717
'--name': `name for lib or web-component (default: "name" in package.json or entry filename)`,
18-
'--keepAlive': `keep component alive when web-component is detached? (default: ${defaults.keepAlive})`,
19-
'--shadow': `use shadow DOM when building as web-component? (default: ${defaults.shadow})`
18+
'--keepAlive': `keep component alive when web-component is detached? (default: ${defaults.keepAlive})`
2019
}
2120
}, args => {
2221
for (const key in defaults) {
2322
if (args[key] == null) args[key] = defaults[key]
2423
}
24+
if (args.dest == null) {
25+
args.dest = options.outputDir
26+
}
2527
api.setMode(args.mode)
2628

2729
const chalk = require('chalk')
@@ -43,16 +45,16 @@ module.exports = (api, options) => {
4345
}
4446

4547
return new Promise((resolve, reject) => {
46-
const targetDir = api.resolve(options.outputDir)
48+
const targetDir = api.resolve(args.dest)
4749
rimraf(targetDir, err => {
4850
if (err) {
4951
return reject(err)
5052
}
5153
let webpackConfig
5254
if (args.target === 'lib') {
53-
webpackConfig = require('./resolveLibConfig')(api, args)
55+
webpackConfig = require('./resolveLibConfig')(api, args, options)
5456
} else if (args.target === 'web-component') {
55-
webpackConfig = require('./resolveWebComponentConfig')(api, args)
57+
webpackConfig = require('./resolveWebComponentConfig')(api, args, options)
5658
} else {
5759
webpackConfig = api.resolveWebpackConfig()
5860
}
@@ -66,7 +68,7 @@ module.exports = (api, options) => {
6668
process.stdout.write(stats.toString({
6769
colors: true,
6870
modules: false,
69-
children: api.hasPlugin('typescript'),
71+
children: api.hasPlugin('typescript') || args.target !== 'app',
7072
chunks: false,
7173
chunkModules: false
7274
}) + '\n\n')

packages/@vue/cli-service/lib/commands/build/resolveLibConfig.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module.exports = (api, { entry, name }) => {
1+
module.exports = (api, { entry, name, dest }, options) => {
22
const libName = name || api.service.pkg.name || entry.replace(/\.(js|vue)$/, '')
33
// setting this disables app-only configs
44
process.env.VUE_CLI_TARGET = 'lib'
@@ -7,17 +7,20 @@ module.exports = (api, { entry, name }) => {
77

88
api.chainWebpack(config => {
99
config.output
10+
.path(api.resolve(dest))
1011
.filename(`[name].js`)
1112
.library(libName)
1213
.libraryExport('default')
1314

1415
// adjust css output name
15-
config
16-
.plugin('extract-css')
17-
.tap(args => {
18-
args[0].filename = `${libName}.css`
19-
return args
20-
})
16+
if (options.css.extract !== false) {
17+
config
18+
.plugin('extract-css')
19+
.tap(args => {
20+
args[0].filename = `${libName}.css`
21+
return args
22+
})
23+
}
2124

2225
// only minify min entry
2326
config
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module.exports = (api, { entry, name, keepAlive, shadow }) => {
1+
module.exports = (api, { entry, name, dest, keepAlive }) => {
22
const libName = name || api.service.pkg.name || entry.replace(/\.(js|vue)$/, '')
33
if (libName.indexOf('-') < 0) {
44
const { log, error } = require('@vue/cli-shared-utils')
@@ -11,19 +11,29 @@ module.exports = (api, { entry, name, keepAlive, shadow }) => {
1111
process.env.VUE_CLI_TARGET = 'web-component'
1212
// inline all static asset files since there is no publicPath handling
1313
process.env.VUE_CLI_INLINE_LIMIT = Infinity
14+
// Disable CSS extraction and turn on CSS shadow mode for vue-style-loader
15+
process.env.VUE_CLI_CSS_SHADOW_MODE = true
1416

1517
api.chainWebpack(config => {
18+
config.entryPoints.clear()
19+
// set proxy entry for *.vue files
20+
if (/\.vue$/.test(entry)) {
21+
config
22+
.entry(libName)
23+
.add(require.resolve('./entry-web-component.js'))
24+
config.resolve
25+
.alias
26+
.set('~entry', api.resolve(entry))
27+
} else {
28+
config
29+
.entry(libName)
30+
.add(api.resolve(entry))
31+
}
32+
1633
config.output
34+
.path(api.resolve(dest))
1735
.filename(`[name].js`)
1836

19-
// only minify min entry
20-
config
21-
.plugin('uglify')
22-
.tap(args => {
23-
args[0].include = /\.min\.js$/
24-
return args
25-
})
26-
2737
// externalize Vue in case user imports it
2838
config
2939
.externals({
@@ -35,37 +45,19 @@ module.exports = (api, { entry, name, keepAlive, shadow }) => {
3545
.use(require('webpack/lib/DefinePlugin'), [{
3646
'process.env': {
3747
CUSTOM_ELEMENT_NAME: JSON.stringify(libName),
38-
CUSTOM_ELEMENT_KEEP_ALIVE: keepAlive,
39-
CUSTOM_ELEMENT_USE_SHADOW_DOM: shadow
48+
CUSTOM_ELEMENT_KEEP_ALIVE: keepAlive
4049
}
4150
}])
4251

43-
// TODO handle CSS (insert in shadow DOM)
52+
// enable shadow mode in vue-loader
53+
config.module
54+
.rule('vue')
55+
.use('vue-loader')
56+
.tap(options => {
57+
options.shadowMode = true
58+
return options
59+
})
4460
})
4561

46-
function genConfig (postfix) {
47-
postfix = postfix ? `.${postfix}` : ``
48-
api.chainWebpack(config => {
49-
config.entryPoints.clear()
50-
// set proxy entry for *.vue files
51-
if (/\.vue$/.test(entry)) {
52-
config
53-
.entry(`${libName}${postfix}`)
54-
.add(require.resolve('./entry-web-component.js'))
55-
config.resolve
56-
.alias
57-
.set('~entry', api.resolve(entry))
58-
} else {
59-
config
60-
.entry(`${libName}${postfix}`)
61-
.add(api.resolve(entry))
62-
}
63-
})
64-
return api.resolveWebpackConfig()
65-
}
66-
67-
return [
68-
genConfig(''),
69-
genConfig('min')
70-
]
62+
return api.resolveWebpackConfig()
7163
}

packages/@vue/cli-service/lib/webpack/CSSLoaderResolver.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module.exports = class CSSLoaderResolver {
2929
this.cssLoader = 'css-loader'
3030
this.fallbackLoader = 'vue-style-loader'
3131
this.sourceMap = sourceMap
32-
this.extract = extract
32+
this.extract = extract && !process.env.VUE_CLI_CSS_SHADOW_MODE
3333
this.minimize = minimize
3434
this.modules = modules
3535
this.postcss = postcss
@@ -83,6 +83,7 @@ module.exports = class CSSLoaderResolver {
8383
}) : [{
8484
loader: this.fallbackLoader,
8585
options: {
86+
shadowMode: !!process.env.VUE_CLI_CSS_SHADOW_MODE,
8687
sourceMap: this.sourceMap
8788
}
8889
}, ...use]

packages/@vue/cli-service/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"thread-loader": "^1.1.2",
5656
"uglifyjs-webpack-plugin": "^1.1.6",
5757
"url-loader": "^0.6.2",
58-
"vue-loader": "^13.7.0",
58+
"vue-loader": "^14.0.0",
5959
"vue-style-loader": "^3.1.1",
6060
"vue-template-compiler": "^2.5.13",
6161
"webpack": "^3.10.0",

packages/@vue/cli/bin/vue.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ program
6666
.command('build [entry]')
6767
.option('-t, --target <target>', 'Build target (app | lib | web-component, default: app)')
6868
.option('-n, --name <name>', 'name for lib or web-component (default: entry filename)')
69+
.option('-d, --dest <dir>', 'output directory (default: dist)')
6970
.option('--keepAlive', 'keep component alive when web-component is detached? (default: false)')
70-
.option('--shadow', 'use shadow DOM when building as web-component? (default: true)')
7171
.description('build a .js or .vue file in production mode with zero config')
7272
.action((entry, cmd) => {
7373
loadCommand('build', '@vue/cli-service-global').build(entry, cleanArgs(cmd))

yarn.lock

+10-10
Original file line numberDiff line numberDiff line change
@@ -10077,9 +10077,9 @@ vue-jest@^2.0.0:
1007710077
tsconfig "^7.0.0"
1007810078
vue-template-es2015-compiler "^1.5.3"
1007910079

10080-
vue-loader@^13.7.0:
10081-
version "13.7.0"
10082-
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-%3Cspan%20class="x x-first x-last">13.7.0.tgz#4d6a35b169c2a0a488842fb95c85052105fa9729"
10080+
vue-loader@^14.0.0:
10081+
version "14.0.0"
10082+
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-%3Cspan%20class="x x-first x-last">14.0.0.tgz#47175b739309b25d3b1f6b48466e72332093f533"
1008310083
dependencies:
1008410084
consolidate "^0.14.0"
1008510085
hash-sum "^1.0.2"
@@ -10092,7 +10092,7 @@ vue-loader@^13.7.0:
1009210092
resolve "^1.4.0"
1009310093
source-map "^0.6.1"
1009410094
vue-hot-reload-api "^2.2.0"
10095-
vue-style-loader "^3.0.0"
10095+
vue-style-loader "^4.0.1"
1009610096
vue-template-es2015-compiler "^1.6.0"
1009710097

1009810098
vue-parser@^1.1.5:
@@ -10112,16 +10112,16 @@ vue-router@^3.0.1:
1011210112
version "3.0.1"
1011310113
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9"
1011410114

10115-
vue-style-loader@^3.0.0:
10116-
version "3.0.3"
10117-
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.%3Cspan%20class="x x-first x-last">0.3.tgz#623658f81506aef9d121cdc113a4f5c9cac32df7"
10115+
vue-style-loader@^3.1.1:
10116+
version "3.1.1"
10117+
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.%3Cspan%20class="x x-first x-last">1.1.tgz#74fdef91a81d38bc0125746a1b5505e62d69e32c"
1011810118
dependencies:
1011910119
hash-sum "^1.0.2"
1012010120
loader-utils "^1.0.2"
1012110121

10122-
vue-style-loader@^3.1.1:
10123-
version "3.1.1"
10124-
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-%3Cspan%20class="x x-first x-last">3.1.1.tgz#74fdef91a81d38bc0125746a1b5505e62d69e32c"
10122+
vue-style-loader@^4.0.1:
10123+
version "4.0.1"
10124+
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-%3Cspan%20class="x x-first x-last">4.0.1.tgz#252300d32eb97e83c1a1cb5b2029e2d8c3adcf9f"
1012510125
dependencies:
1012610126
hash-sum "^1.0.2"
1012710127
loader-utils "^1.0.2"

0 commit comments

Comments
 (0)