mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-03-21 02:21:00 -05:00
feat: inject styles under shadow root in web component mode
This commit is contained in:
@@ -12,10 +12,6 @@ const name = process.env.CUSTOM_ELEMENT_NAME
|
||||
// - true: the instance is always kept alive
|
||||
const keepAlive = process.env.CUSTOM_ELEMENT_KEEP_ALIVE
|
||||
|
||||
// Whether to use Shadow DOM.
|
||||
// default: true
|
||||
const useShadowDOM = process.env.CUSTOM_ELEMENT_USE_SHADOW_DOM
|
||||
|
||||
const options = typeof Component === 'function'
|
||||
? Component.options
|
||||
: Component
|
||||
@@ -30,6 +26,10 @@ const props = Array.isArray(options.props)
|
||||
: options.props || {}
|
||||
const propsList = Object.keys(props)
|
||||
|
||||
// CSS injection function exposed by vue-loader & vue-style-loader
|
||||
const styleInjectors = window[options.__shadowInjectId]
|
||||
const injectStyle = root => styleInjectors.forEach(inject => inject(root))
|
||||
|
||||
// TODO use ES5 syntax
|
||||
class CustomElement extends HTMLElement {
|
||||
static get observedAttributes () {
|
||||
@@ -49,21 +49,15 @@ class CustomElement extends HTMLElement {
|
||||
})
|
||||
|
||||
this._attached = false
|
||||
if (useShadowDOM) {
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' })
|
||||
}
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' })
|
||||
injectStyle(this._shadowRoot)
|
||||
}
|
||||
|
||||
connectedCallback () {
|
||||
this._attached = true
|
||||
if (!this._wrapper._isMounted) {
|
||||
this._wrapper.$mount()
|
||||
const el = this._wrapper.$el
|
||||
if (useShadowDOM) {
|
||||
this._shadowRoot.appendChild(el)
|
||||
} else {
|
||||
this.appendChild(el)
|
||||
}
|
||||
this._shadowRoot.appendChild(this._wrapper.$el)
|
||||
}
|
||||
this._wrapper._data._active = true
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ const defaults = {
|
||||
mode: 'production',
|
||||
target: 'app',
|
||||
entry: 'src/App.vue',
|
||||
keepAlive: false,
|
||||
shadow: true
|
||||
keepAlive: false
|
||||
}
|
||||
|
||||
module.exports = (api, options) => {
|
||||
@@ -12,16 +11,19 @@ module.exports = (api, options) => {
|
||||
usage: 'vue-cli-service build [options]',
|
||||
options: {
|
||||
'--mode': `specify env mode (default: ${defaults.mode})`,
|
||||
'--dest': `specify output directory (default: ${options.outputDir})`,
|
||||
'--target': `app | lib | web-component (default: ${defaults.target})`,
|
||||
'--entry': `entry for lib or web-component (default: ${defaults.entry})`,
|
||||
'--name': `name for lib or web-component (default: "name" in package.json or entry filename)`,
|
||||
'--keepAlive': `keep component alive when web-component is detached? (default: ${defaults.keepAlive})`,
|
||||
'--shadow': `use shadow DOM when building as web-component? (default: ${defaults.shadow})`
|
||||
'--keepAlive': `keep component alive when web-component is detached? (default: ${defaults.keepAlive})`
|
||||
}
|
||||
}, args => {
|
||||
for (const key in defaults) {
|
||||
if (args[key] == null) args[key] = defaults[key]
|
||||
}
|
||||
if (args.dest == null) {
|
||||
args.dest = options.outputDir
|
||||
}
|
||||
api.setMode(args.mode)
|
||||
|
||||
const chalk = require('chalk')
|
||||
@@ -43,16 +45,16 @@ module.exports = (api, options) => {
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const targetDir = api.resolve(options.outputDir)
|
||||
const targetDir = api.resolve(args.dest)
|
||||
rimraf(targetDir, err => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
let webpackConfig
|
||||
if (args.target === 'lib') {
|
||||
webpackConfig = require('./resolveLibConfig')(api, args)
|
||||
webpackConfig = require('./resolveLibConfig')(api, args, options)
|
||||
} else if (args.target === 'web-component') {
|
||||
webpackConfig = require('./resolveWebComponentConfig')(api, args)
|
||||
webpackConfig = require('./resolveWebComponentConfig')(api, args, options)
|
||||
} else {
|
||||
webpackConfig = api.resolveWebpackConfig()
|
||||
}
|
||||
@@ -66,7 +68,7 @@ module.exports = (api, options) => {
|
||||
process.stdout.write(stats.toString({
|
||||
colors: true,
|
||||
modules: false,
|
||||
children: api.hasPlugin('typescript'),
|
||||
children: api.hasPlugin('typescript') || args.target !== 'app',
|
||||
chunks: false,
|
||||
chunkModules: false
|
||||
}) + '\n\n')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = (api, { entry, name }) => {
|
||||
module.exports = (api, { entry, name, dest }, options) => {
|
||||
const libName = name || api.service.pkg.name || entry.replace(/\.(js|vue)$/, '')
|
||||
// setting this disables app-only configs
|
||||
process.env.VUE_CLI_TARGET = 'lib'
|
||||
@@ -7,17 +7,20 @@ module.exports = (api, { entry, name }) => {
|
||||
|
||||
api.chainWebpack(config => {
|
||||
config.output
|
||||
.path(api.resolve(dest))
|
||||
.filename(`[name].js`)
|
||||
.library(libName)
|
||||
.libraryExport('default')
|
||||
|
||||
// adjust css output name
|
||||
config
|
||||
.plugin('extract-css')
|
||||
.tap(args => {
|
||||
args[0].filename = `${libName}.css`
|
||||
return args
|
||||
})
|
||||
if (options.css.extract !== false) {
|
||||
config
|
||||
.plugin('extract-css')
|
||||
.tap(args => {
|
||||
args[0].filename = `${libName}.css`
|
||||
return args
|
||||
})
|
||||
}
|
||||
|
||||
// only minify min entry
|
||||
config
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = (api, { entry, name, keepAlive, shadow }) => {
|
||||
module.exports = (api, { entry, name, dest, keepAlive }) => {
|
||||
const libName = name || api.service.pkg.name || entry.replace(/\.(js|vue)$/, '')
|
||||
if (libName.indexOf('-') < 0) {
|
||||
const { log, error } = require('@vue/cli-shared-utils')
|
||||
@@ -11,18 +11,28 @@ module.exports = (api, { entry, name, keepAlive, shadow }) => {
|
||||
process.env.VUE_CLI_TARGET = 'web-component'
|
||||
// inline all static asset files since there is no publicPath handling
|
||||
process.env.VUE_CLI_INLINE_LIMIT = Infinity
|
||||
// Disable CSS extraction and turn on CSS shadow mode for vue-style-loader
|
||||
process.env.VUE_CLI_CSS_SHADOW_MODE = true
|
||||
|
||||
api.chainWebpack(config => {
|
||||
config.output
|
||||
.filename(`[name].js`)
|
||||
config.entryPoints.clear()
|
||||
// set proxy entry for *.vue files
|
||||
if (/\.vue$/.test(entry)) {
|
||||
config
|
||||
.entry(libName)
|
||||
.add(require.resolve('./entry-web-component.js'))
|
||||
config.resolve
|
||||
.alias
|
||||
.set('~entry', api.resolve(entry))
|
||||
} else {
|
||||
config
|
||||
.entry(libName)
|
||||
.add(api.resolve(entry))
|
||||
}
|
||||
|
||||
// only minify min entry
|
||||
config
|
||||
.plugin('uglify')
|
||||
.tap(args => {
|
||||
args[0].include = /\.min\.js$/
|
||||
return args
|
||||
})
|
||||
config.output
|
||||
.path(api.resolve(dest))
|
||||
.filename(`[name].js`)
|
||||
|
||||
// externalize Vue in case user imports it
|
||||
config
|
||||
@@ -35,37 +45,19 @@ module.exports = (api, { entry, name, keepAlive, shadow }) => {
|
||||
.use(require('webpack/lib/DefinePlugin'), [{
|
||||
'process.env': {
|
||||
CUSTOM_ELEMENT_NAME: JSON.stringify(libName),
|
||||
CUSTOM_ELEMENT_KEEP_ALIVE: keepAlive,
|
||||
CUSTOM_ELEMENT_USE_SHADOW_DOM: shadow
|
||||
CUSTOM_ELEMENT_KEEP_ALIVE: keepAlive
|
||||
}
|
||||
}])
|
||||
|
||||
// TODO handle CSS (insert in shadow DOM)
|
||||
// enable shadow mode in vue-loader
|
||||
config.module
|
||||
.rule('vue')
|
||||
.use('vue-loader')
|
||||
.tap(options => {
|
||||
options.shadowMode = true
|
||||
return options
|
||||
})
|
||||
})
|
||||
|
||||
function genConfig (postfix) {
|
||||
postfix = postfix ? `.${postfix}` : ``
|
||||
api.chainWebpack(config => {
|
||||
config.entryPoints.clear()
|
||||
// set proxy entry for *.vue files
|
||||
if (/\.vue$/.test(entry)) {
|
||||
config
|
||||
.entry(`${libName}${postfix}`)
|
||||
.add(require.resolve('./entry-web-component.js'))
|
||||
config.resolve
|
||||
.alias
|
||||
.set('~entry', api.resolve(entry))
|
||||
} else {
|
||||
config
|
||||
.entry(`${libName}${postfix}`)
|
||||
.add(api.resolve(entry))
|
||||
}
|
||||
})
|
||||
return api.resolveWebpackConfig()
|
||||
}
|
||||
|
||||
return [
|
||||
genConfig(''),
|
||||
genConfig('min')
|
||||
]
|
||||
return api.resolveWebpackConfig()
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ module.exports = class CSSLoaderResolver {
|
||||
this.cssLoader = 'css-loader'
|
||||
this.fallbackLoader = 'vue-style-loader'
|
||||
this.sourceMap = sourceMap
|
||||
this.extract = extract
|
||||
this.extract = extract && !process.env.VUE_CLI_CSS_SHADOW_MODE
|
||||
this.minimize = minimize
|
||||
this.modules = modules
|
||||
this.postcss = postcss
|
||||
@@ -83,6 +83,7 @@ module.exports = class CSSLoaderResolver {
|
||||
}) : [{
|
||||
loader: this.fallbackLoader,
|
||||
options: {
|
||||
shadowMode: !!process.env.VUE_CLI_CSS_SHADOW_MODE,
|
||||
sourceMap: this.sourceMap
|
||||
}
|
||||
}, ...use]
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"thread-loader": "^1.1.2",
|
||||
"uglifyjs-webpack-plugin": "^1.1.6",
|
||||
"url-loader": "^0.6.2",
|
||||
"vue-loader": "^13.7.0",
|
||||
"vue-loader": "^14.0.0",
|
||||
"vue-style-loader": "^3.1.1",
|
||||
"vue-template-compiler": "^2.5.13",
|
||||
"webpack": "^3.10.0",
|
||||
|
||||
@@ -66,8 +66,8 @@ program
|
||||
.command('build [entry]')
|
||||
.option('-t, --target <target>', 'Build target (app | lib | web-component, default: app)')
|
||||
.option('-n, --name <name>', 'name for lib or web-component (default: entry filename)')
|
||||
.option('-d, --dest <dir>', 'output directory (default: dist)')
|
||||
.option('--keepAlive', 'keep component alive when web-component is detached? (default: false)')
|
||||
.option('--shadow', 'use shadow DOM when building as web-component? (default: true)')
|
||||
.description('build a .js or .vue file in production mode with zero config')
|
||||
.action((entry, cmd) => {
|
||||
loadCommand('build', '@vue/cli-service-global').build(entry, cleanArgs(cmd))
|
||||
|
||||
20
yarn.lock
20
yarn.lock
@@ -10077,9 +10077,9 @@ vue-jest@^2.0.0:
|
||||
tsconfig "^7.0.0"
|
||||
vue-template-es2015-compiler "^1.5.3"
|
||||
|
||||
vue-loader@^13.7.0:
|
||||
version "13.7.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-13.7.0.tgz#4d6a35b169c2a0a488842fb95c85052105fa9729"
|
||||
vue-loader@^14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-14.0.0.tgz#47175b739309b25d3b1f6b48466e72332093f533"
|
||||
dependencies:
|
||||
consolidate "^0.14.0"
|
||||
hash-sum "^1.0.2"
|
||||
@@ -10092,7 +10092,7 @@ vue-loader@^13.7.0:
|
||||
resolve "^1.4.0"
|
||||
source-map "^0.6.1"
|
||||
vue-hot-reload-api "^2.2.0"
|
||||
vue-style-loader "^3.0.0"
|
||||
vue-style-loader "^4.0.1"
|
||||
vue-template-es2015-compiler "^1.6.0"
|
||||
|
||||
vue-parser@^1.1.5:
|
||||
@@ -10112,16 +10112,16 @@ vue-router@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9"
|
||||
|
||||
vue-style-loader@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.0.3.tgz#623658f81506aef9d121cdc113a4f5c9cac32df7"
|
||||
vue-style-loader@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.1.1.tgz#74fdef91a81d38bc0125746a1b5505e62d69e32c"
|
||||
dependencies:
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
vue-style-loader@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.1.1.tgz#74fdef91a81d38bc0125746a1b5505e62d69e32c"
|
||||
vue-style-loader@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.0.1.tgz#252300d32eb97e83c1a1cb5b2029e2d8c3adcf9f"
|
||||
dependencies:
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
Reference in New Issue
Block a user