-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: build --target web-component (WIP)
- Loading branch information
Showing
7 changed files
with
212 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 99 additions & 2 deletions
101
packages/@vue/cli-service/lib/commands/build/entry-web-component.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,103 @@ | ||
// TODO | ||
/* global HTMLElement */ | ||
|
||
import Vue from 'vue' | ||
import Component from '~entry' | ||
|
||
new Vue(Component) | ||
// Name to register the custom element as. Must contain a hyphen. | ||
const name = process.env.CUSTOM_ELEMENT_NAME | ||
|
||
// Whether to keep the instance alive when element is removed from DOM. | ||
// Default: false. | ||
// - false: the instance is destroyed and recreated when element is removed / reinserted | ||
// - 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 | ||
|
||
const arrToObj = (arr, defaultValue) => arr.reduce((acc, key) => { | ||
acc[key] = defaultValue | ||
return acc | ||
}, {}) | ||
|
||
const props = Array.isArray(options.props) | ||
? arrToObj(options.props, {}) | ||
: options.props || {} | ||
const propsList = Object.keys(props) | ||
|
||
// TODO use ES5 syntax | ||
class CustomElement extends HTMLElement { | ||
static get observedAttributes () { | ||
return propsList | ||
} | ||
|
||
constructor () { | ||
super() | ||
|
||
const data = arrToObj(propsList) | ||
data._active = false | ||
this._wrapper = new Vue({ | ||
data, | ||
render: h => data._active | ||
? h(Component, { props: this._data }) | ||
: null | ||
}) | ||
|
||
this._attached = false | ||
if (useShadowDOM) { | ||
this._shadowRoot = this.attachShadow({ mode: 'open' }) | ||
} | ||
} | ||
|
||
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._wrapper._data._active = true | ||
} | ||
|
||
disconnectedCallback () { | ||
this._attached = false | ||
const destroy = () => { | ||
this._wrapper._data._active = false | ||
} | ||
if (!keepAlive) { | ||
destroy() | ||
} else if (typeof keepAlive === 'number') { | ||
setTimeout(() => { | ||
if (!this._attached) destroy() | ||
}, keepAlive) | ||
} | ||
} | ||
|
||
attributeChangedCallback (attrName, oldVal, newVal) { | ||
this._wrapper._data[attrName] = newVal | ||
} | ||
} | ||
|
||
propsList.forEach(key => { | ||
Object.defineProperty(CustomElement.prototype, key, { | ||
get () { | ||
return this._wrapper._data[key] | ||
}, | ||
set (newVal) { | ||
this._wrapper._data[key] = newVal | ||
}, | ||
enumerable: false, | ||
configurable: true | ||
}) | ||
}) | ||
|
||
window.customElements.define(name, CustomElement) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 44 additions & 37 deletions
81
packages/@vue/cli-service/lib/commands/build/resolveLibConfig.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 54 additions & 31 deletions
85
packages/@vue/cli-service/lib/commands/build/resolveWebComponentConfig.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,71 @@ | ||
module.exports = (api, { libEntry, libName }) => { | ||
const genConfig = postfix => { | ||
api.chainWebpack(config => { | ||
libName = libName || api.service.pkg.name || libEntry.replace(/\.(js|vue)$/, '') | ||
module.exports = (api, { entry, name, keepAlive, shadow }) => { | ||
const libName = name || api.service.pkg.name || entry.replace(/\.(js|vue)$/, '') | ||
if (libName.indexOf('-') < 0) { | ||
const { log, error } = require('@vue/cli-shared-utils') | ||
log() | ||
error(`--name must contain a hyphen when building as web-component. (got "${libName}")`) | ||
process.exit(1) | ||
} | ||
|
||
// setting this disables app-only configs | ||
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 | ||
|
||
api.chainWebpack(config => { | ||
config.output | ||
.filename(`[name].js`) | ||
|
||
// only minify min entry | ||
config | ||
.plugin('uglify') | ||
.tap(args => { | ||
args[0].include = /\.min\.js$/ | ||
return args | ||
}) | ||
|
||
// externalize Vue in case user imports it | ||
config | ||
.externals({ | ||
vue: 'Vue' | ||
}) | ||
|
||
config | ||
.plugin('web-component-options') | ||
.use(require('webpack/lib/DefinePlugin'), [{ | ||
'process.env': { | ||
CUSTOM_ELEMENT_NAME: JSON.stringify(libName), | ||
CUSTOM_ELEMENT_KEEP_ALIVE: keepAlive, | ||
CUSTOM_ELEMENT_USE_SHADOW_DOM: shadow | ||
} | ||
}]) | ||
|
||
// TODO handle CSS (insert in shadow DOM) | ||
}) | ||
|
||
function genConfig (postfix) { | ||
postfix = postfix ? `.${postfix}` : `` | ||
api.chainWebpack(config => { | ||
config.entryPoints.clear() | ||
// set proxy entry for *.vue files | ||
if (/\.vue$/.test(libEntry)) { | ||
if (/\.vue$/.test(entry)) { | ||
config | ||
.entry(`${libName}.${postfix}`) | ||
.entry(`${libName}${postfix}`) | ||
.add(require.resolve('./entry-web-component.js')) | ||
config.resolve | ||
.alias | ||
.set('~entry', api.resolve(libEntry)) | ||
.set('~entry', api.resolve(entry)) | ||
} else { | ||
config | ||
.entry(`${libName}.${postfix}`) | ||
.add(api.resolve(libEntry)) | ||
.entry(`${libName}${postfix}`) | ||
.add(api.resolve(entry)) | ||
} | ||
|
||
config.output | ||
.filename(`[name].js`) | ||
|
||
// only minify min entry | ||
config | ||
.plugin('uglify') | ||
.tap(args => { | ||
args[0].include = /\.min\.js$/ | ||
return args | ||
}) | ||
|
||
// externalize Vue in case user imports it | ||
config | ||
.externals({ | ||
vue: 'Vue' | ||
}) | ||
|
||
// TODO handle CSS (insert in shadow DOM) | ||
}) | ||
|
||
return api.resolveWebpackConfig() | ||
} | ||
|
||
return [ | ||
genConfig('web-component'), | ||
genConfig('web-component.min') | ||
genConfig(''), | ||
genConfig('min') | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters