diff --git a/packages/@vue/cli-service/lib/commands/build/entry-web-component.js b/packages/@vue/cli-service/lib/commands/build/entry-web-component.js index 729b08824..7632a82a0 100644 --- a/packages/@vue/cli-service/lib/commands/build/entry-web-component.js +++ b/packages/@vue/cli-service/lib/commands/build/entry-web-component.js @@ -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 } diff --git a/packages/@vue/cli-service/lib/commands/build/index.js b/packages/@vue/cli-service/lib/commands/build/index.js index a356e4340..adec13db3 100644 --- a/packages/@vue/cli-service/lib/commands/build/index.js +++ b/packages/@vue/cli-service/lib/commands/build/index.js @@ -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') diff --git a/packages/@vue/cli-service/lib/commands/build/resolveLibConfig.js b/packages/@vue/cli-service/lib/commands/build/resolveLibConfig.js index fc2892114..122d55f86 100644 --- a/packages/@vue/cli-service/lib/commands/build/resolveLibConfig.js +++ b/packages/@vue/cli-service/lib/commands/build/resolveLibConfig.js @@ -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 diff --git a/packages/@vue/cli-service/lib/commands/build/resolveWebComponentConfig.js b/packages/@vue/cli-service/lib/commands/build/resolveWebComponentConfig.js index 018e0c074..607848548 100644 --- a/packages/@vue/cli-service/lib/commands/build/resolveWebComponentConfig.js +++ b/packages/@vue/cli-service/lib/commands/build/resolveWebComponentConfig.js @@ -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() } diff --git a/packages/@vue/cli-service/lib/webpack/CSSLoaderResolver.js b/packages/@vue/cli-service/lib/webpack/CSSLoaderResolver.js index 6668a0bb9..262526459 100644 --- a/packages/@vue/cli-service/lib/webpack/CSSLoaderResolver.js +++ b/packages/@vue/cli-service/lib/webpack/CSSLoaderResolver.js @@ -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] diff --git a/packages/@vue/cli-service/package.json b/packages/@vue/cli-service/package.json index 6c987838f..fe678e8b2 100644 --- a/packages/@vue/cli-service/package.json +++ b/packages/@vue/cli-service/package.json @@ -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", diff --git a/packages/@vue/cli/bin/vue.js b/packages/@vue/cli/bin/vue.js index cf4dbb524..3186df9f9 100755 --- a/packages/@vue/cli/bin/vue.js +++ b/packages/@vue/cli/bin/vue.js @@ -66,8 +66,8 @@ program .command('build [entry]') .option('-t, --target ', 'Build target (app | lib | web-component, default: app)') .option('-n, --name ', 'name for lib or web-component (default: entry filename)') + .option('-d, --dest ', '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)) diff --git a/yarn.lock b/yarn.lock index 4cab602a7..12498161b 100644 --- a/yarn.lock +++ b/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"