Skip to content

Commit 50fdd9b

Browse files
committedFeb 3, 2018
feat: build --target wc-async
1 parent 2c61d23 commit 50fdd9b

14 files changed

+148
-130
lines changed
 

‎.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ node_modules
22
template
33
packages/test
44
temp
5+
entry-wc.js

‎packages/@vue/cli-service/__tests__/buildWc.spec.js

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
jest.setTimeout(30000)
1+
jest.setTimeout(15000)
22

33
const path = require('path')
44
const portfinder = require('portfinder')
@@ -8,10 +8,10 @@ const create = require('@vue/cli-test-utils/createTestProject')
88
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')
99

1010
let server, browser, page
11-
test('build as single wc', async () => {
11+
test('build as wc', async () => {
1212
const project = await create('build-wc', defaultPreset)
1313

14-
const { stdout } = await project.run('vue-cli-service build --target wc src/components/HelloWorld.vue')
14+
const { stdout } = await project.run(`vue-cli-service build --target wc **/*.vue`)
1515
expect(stdout).toMatch('Build complete.')
1616

1717
expect(project.has('dist/demo.html')).toBe(true)
@@ -33,17 +33,24 @@ test('build as single wc', async () => {
3333
page = launched.page
3434

3535
const styleCount = await page.evaluate(() => {
36-
return document.querySelector('build-wc').shadowRoot.querySelectorAll('style').length
36+
return document.querySelector('build-wc-app').shadowRoot.querySelectorAll('style').length
3737
})
38-
expect(styleCount).toBe(1)
38+
expect(styleCount).toBe(2) // should contain styles from both app and child
3939

40-
await page.evaluate(() => {
41-
document.querySelector('build-wc').msg = 'hello'
42-
})
4340
const h1Text = await page.evaluate(() => {
44-
return document.querySelector('build-wc').shadowRoot.querySelector('h1').textContent
41+
return document.querySelector('build-wc-app').shadowRoot.querySelector('h1').textContent
42+
})
43+
expect(h1Text).toMatch('Welcome to Your Vue.js App')
44+
45+
const childStyleCount = await page.evaluate(() => {
46+
return document.querySelector('build-wc-hello-world').shadowRoot.querySelectorAll('style').length
47+
})
48+
expect(childStyleCount).toBe(1)
49+
50+
const h2Text = await page.evaluate(() => {
51+
return document.querySelector('build-wc-hello-world').shadowRoot.querySelector('h2').textContent
4552
})
46-
expect(h1Text).toBe('hello')
53+
expect(h2Text).toMatch('Essential Links')
4754
})
4855

4956
afterAll(async () => {

‎packages/@vue/cli-service/__tests__/buildMultiWc.spec.js renamed to ‎packages/@vue/cli-service/__tests__/buildWcAsync.spec.js

+16-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
jest.setTimeout(30000)
1+
jest.setTimeout(15000)
22

33
const path = require('path')
44
const portfinder = require('portfinder')
@@ -8,15 +8,21 @@ const create = require('@vue/cli-test-utils/createTestProject')
88
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')
99

1010
let server, browser, page
11-
test('build as single wc', async () => {
12-
const project = await create('build-multi-wc', defaultPreset)
11+
test('build as wc in async mode', async () => {
12+
const project = await create('build-wc-async', defaultPreset)
1313

14-
const { stdout } = await project.run(`vue-cli-service build --target multi-wc **/*.vue`)
14+
const { stdout } = await project.run(`vue-cli-service build --target wc-async **/*.vue`)
1515
expect(stdout).toMatch('Build complete.')
1616

1717
expect(project.has('dist/demo.html')).toBe(true)
18-
expect(project.has('dist/build-multi-wc.js')).toBe(true)
19-
expect(project.has('dist/build-multi-wc.min.js')).toBe(true)
18+
expect(project.has('dist/build-wc-async.js')).toBe(true)
19+
expect(project.has('dist/build-wc-async.min.js')).toBe(true)
20+
21+
// code-split chunks
22+
expect(project.has('dist/build-wc-async.0.js')).toBe(true)
23+
expect(project.has('dist/build-wc-async.0.min.js')).toBe(true)
24+
expect(project.has('dist/build-wc-async.1.js')).toBe(true)
25+
expect(project.has('dist/build-wc-async.1.min.js')).toBe(true)
2026

2127
const port = await portfinder.getPortPromise()
2228
server = createServer({ root: path.join(project.dir, 'dist') })
@@ -33,22 +39,22 @@ test('build as single wc', async () => {
3339
page = launched.page
3440

3541
const styleCount = await page.evaluate(() => {
36-
return document.querySelector('build-multi-wc-app').shadowRoot.querySelectorAll('style').length
42+
return document.querySelector('build-wc-async-app').shadowRoot.querySelectorAll('style').length
3743
})
3844
expect(styleCount).toBe(2) // should contain styles from both app and child
3945

4046
const h1Text = await page.evaluate(() => {
41-
return document.querySelector('build-multi-wc-app').shadowRoot.querySelector('h1').textContent
47+
return document.querySelector('build-wc-async-app').shadowRoot.querySelector('h1').textContent
4248
})
4349
expect(h1Text).toMatch('Welcome to Your Vue.js App')
4450

4551
const childStyleCount = await page.evaluate(() => {
46-
return document.querySelector('build-multi-wc-hello-world').shadowRoot.querySelectorAll('style').length
52+
return document.querySelector('build-wc-async-hello-world').shadowRoot.querySelectorAll('style').length
4753
})
4854
expect(childStyleCount).toBe(1)
4955

5056
const h2Text = await page.evaluate(() => {
51-
return document.querySelector('build-multi-wc-hello-world').shadowRoot.querySelector('h2').textContent
57+
return document.querySelector('build-wc-async-hello-world').shadowRoot.querySelector('h2').textContent
5258
})
5359
expect(h2Text).toMatch('Essential Links')
5460
})
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
entry-multi-wc.js
1+
entry-wc.js

‎packages/@vue/cli-service/lib/commands/build/demo-multi-wc.html

-6
This file was deleted.

‎packages/@vue/cli-service/lib/commands/build/demo-wc.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
<script src="https://unpkg.com/vue"></script>
33
<script src="./<%- htmlWebpackPlugin.options.libName %>.js"></script>
44

5-
<<%= htmlWebpackPlugin.options.libName %>></<%= htmlWebpackPlugin.options.libName %>>
5+
<% for (const comp of htmlWebpackPlugin.options.components) { %>
6+
<<%= comp %>></<%= comp %>>
7+
<% } %>
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import Vue from 'vue'
22
import wrap from '@vue/web-component-wrapper'
3-
import Component from '~entry'
43

5-
window.customElements.define(
6-
process.env.CUSTOM_ELEMENT_NAME,
7-
wrap(Vue, Component)
8-
)
4+
// runtime shared by every component chunk
5+
import 'css-loader/lib/css-base'
6+
import 'vue-style-loader/lib/addStylesShadow'
7+
import 'vue-loader/lib/runtime/component-normalizer'
8+
9+
import myWc from '~root/my-wc.vue'
10+
window.customElements.define('my-wc', wrap(Vue, myWc))

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

-42
This file was deleted.

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module.exports = (api, options) => {
1111
options: {
1212
'--mode': `specify env mode (default: ${defaults.mode})`,
1313
'--dest': `specify output directory (default: ${options.outputDir})`,
14-
'--target': `app | lib | wc | multi-wc (default: ${defaults.target})`,
14+
'--target': `app | lib | wc | wc-async (default: ${defaults.target})`,
1515
'--name': `name for lib or (multi-)web-component mode (default: "name" in package.json or entry filename)`
1616
}
1717
}, args => {
@@ -56,8 +56,9 @@ module.exports = (api, options) => {
5656
webpackConfig = require('./resolveLibConfig')(api, args, options)
5757
} else if (
5858
args.target === 'web-component' ||
59+
args.target === 'web-component-async' ||
5960
args.target === 'wc' ||
60-
args.target === 'multi-wc'
61+
args.target === 'wc-async'
6162
) {
6263
webpackConfig = require('./resolveWebComponentConfig')(api, args, options)
6364
} else {

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,30 @@ module.exports = (api, { entry, name, dest }, options) => {
1515
const config = api.resolveChainableWebpackConfig()
1616

1717
config.entryPoints.clear()
18+
const entryName = `${libName}.${postfix}`
1819
// set proxy entry for *.vue files
1920
if (/\.vue$/.test(entry)) {
2021
config
21-
.entry(`${libName}.${postfix}`)
22+
.entry(entryName)
2223
.add(require.resolve('./entry-lib.js'))
2324
config.resolve
2425
.alias
2526
.set('~entry', api.resolve(entry))
2627
} else {
2728
config
28-
.entry(`${libName}.${postfix}`)
29+
.entry(entryName)
2930
.add(api.resolve(entry))
3031
}
3132

3233
config.output
3334
.path(api.resolve(dest))
34-
.filename(`[name].js`)
35+
.filename(`${entryName}.js`)
36+
.chunkFilename(`${entryName}.[id].js`)
3537
.library(libName)
3638
.libraryExport('default')
3739
.libraryTarget(format)
40+
// use relative publicPath so this can be deployed anywhere
41+
.publicPath('./')
3842

3943
// adjust css output name so they write to the same file
4044
if (options.css.extract !== false) {

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

+44-48
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,61 @@
11
const path = require('path')
22
const {
3-
filesToComponentNames,
4-
generateMultiWebComponentEntry
5-
} = require('./generateMultiWcEntry')
3+
resolveEntry,
4+
fileToComponentName
5+
} = require('./resolveWebComponentEntry')
6+
7+
module.exports = (api, { target, entry, name, dest }) => {
8+
// setting this disables app-only configs
9+
process.env.VUE_CLI_TARGET = 'web-component'
10+
// Disable CSS extraction and turn on CSS shadow mode for vue-style-loader
11+
process.env.VUE_CLI_CSS_SHADOW_MODE = true
612

7-
module.exports = (api, { target, entry, name, dest, prefix }) => {
813
const { log, error } = require('@vue/cli-shared-utils')
914
const abort = msg => {
1015
log()
1116
error(msg)
1217
process.exit(1)
1318
}
1419

15-
const libName = (
16-
name ||
17-
api.service.pkg.name ||
18-
path.basename(entry).replace(/\.(jsx?|vue)$/, '')
19-
)
20-
if (libName.indexOf('-') < 0 && target !== 'multi-wc') {
21-
abort(`--name must contain a hyphen with --target web-component. (got "${libName}")`)
22-
}
20+
const isAsync = /async/.test(target)
2321

24-
let dynamicEntry
25-
let resolvedFiles
26-
if (target === 'multi-wc') {
27-
if (!entry) {
28-
abort(`a glob pattern is required with --target multi-web-component.`)
22+
// generate dynamic entry based on glob files
23+
const resolvedFiles = require('globby').sync([entry], { cwd: api.resolve('.') })
24+
if (!resolvedFiles.length) {
25+
abort(`entry pattern "${entry}" did not match any files.`)
26+
}
27+
let libName
28+
let prefix
29+
if (resolvedFiles.length === 1) {
30+
// in single mode, determine the lib name from filename
31+
libName = name || fileToComponentName('', resolvedFiles[0]).kebabName
32+
prefix = ''
33+
if (libName.indexOf('-') < 0) {
34+
abort(`--name must contain a hyphen when building a single web component.`)
2935
}
30-
// generate dynamic entry based on glob files
31-
resolvedFiles = require('globby').sync([entry], { cwd: api.resolve('.') })
32-
if (!resolvedFiles.length) {
33-
abort(`glob pattern "${entry}" did not match any files.`)
36+
} else {
37+
// multi mode
38+
libName = prefix = (name || api.service.pkg.name)
39+
if (!libName) {
40+
abort(`--name is required when building multiple web components.`)
3441
}
35-
dynamicEntry = generateMultiWebComponentEntry(libName, resolvedFiles)
3642
}
3743

38-
// setting this disables app-only configs
39-
process.env.VUE_CLI_TARGET = 'web-component'
40-
// inline all static asset files since there is no publicPath handling
41-
process.env.VUE_CLI_INLINE_LIMIT = Infinity
42-
// Disable CSS extraction and turn on CSS shadow mode for vue-style-loader
43-
process.env.VUE_CLI_CSS_SHADOW_MODE = true
44+
const dynamicEntry = resolveEntry(prefix, resolvedFiles, isAsync)
4445

4546
function genConfig (minify, genHTML) {
4647
const config = api.resolveChainableWebpackConfig()
4748

4849
config.entryPoints.clear()
50+
const entryName = `${libName}${minify ? `.min` : ``}`
4951

5052
// set proxy entry for *.vue files
51-
if (target === 'multi-wc') {
52-
config
53-
.entry(`${libName}${minify ? `.min` : ``}`)
54-
.add(dynamicEntry)
55-
config.resolve
56-
.alias
57-
.set('~root', api.resolve('.'))
58-
} else {
59-
config
60-
.entry(`${libName}${minify ? `.min` : ``}`)
61-
.add(require.resolve('./entry-wc.js'))
62-
config.resolve
63-
.alias
64-
.set('~entry', api.resolve(entry))
65-
}
53+
config
54+
.entry(entryName)
55+
.add(dynamicEntry)
56+
config.resolve
57+
.alias
58+
.set('~root', api.resolve('.'))
6659

6760
// make sure not to transpile wc-wrapper
6861
config.module
@@ -77,7 +70,10 @@ module.exports = (api, { target, entry, name, dest, prefix }) => {
7770

7871
config.output
7972
.path(api.resolve(dest))
80-
.filename(`[name].js`)
73+
.filename(`${entryName}.js`)
74+
.chunkFilename(`${libName}.[id]${minify ? `.min` : ``}.js`)
75+
// use relative publicPath so this can be deployed anywhere
76+
.publicPath('./')
8177

8278
// externalize Vue in case user imports it
8379
config
@@ -106,13 +102,13 @@ module.exports = (api, { target, entry, name, dest, prefix }) => {
106102
config
107103
.plugin('demo-html')
108104
.use(require('html-webpack-plugin'), [{
109-
template: path.resolve(__dirname, `./demo-${target}.html`),
105+
template: path.resolve(__dirname, `./demo-wc.html`),
110106
inject: false,
111107
filename: 'demo.html',
112108
libName,
113-
components: target === 'multi-wc'
114-
? filesToComponentNames(libName, resolvedFiles).map(c => c.kebabName)
115-
: null
109+
components: resolvedFiles.map(file => {
110+
return fileToComponentName(prefix, file).kebabName
111+
})
116112
}])
117113
}
118114

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
4+
const camelizeRE = /-(\w)/g
5+
const camelize = str => {
6+
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
7+
}
8+
9+
const hyphenateRE = /\B([A-Z])/g
10+
const hyphenate = str => {
11+
return str.replace(hyphenateRE, '-$1').toLowerCase()
12+
}
13+
14+
exports.fileToComponentName = (prefix, file) => {
15+
const basename = path.basename(file).replace(/\.(jsx?|vue)$/, '')
16+
const camelName = camelize(basename)
17+
const kebabName = `${prefix ? `${prefix}-` : ``}${hyphenate(basename)}`
18+
return {
19+
basename,
20+
camelName,
21+
kebabName
22+
}
23+
}
24+
25+
exports.resolveEntry = (prefix, files, async) => {
26+
const filePath = path.resolve(__dirname, 'entry-wc.js')
27+
const content = `
28+
import Vue from 'vue'
29+
import wrap from '@vue/web-component-wrapper'
30+
31+
// runtime shared by every component chunk
32+
import 'css-loader/lib/css-base'
33+
import 'vue-style-loader/lib/addStylesShadow'
34+
import 'vue-loader/lib/runtime/component-normalizer'
35+
36+
${files.map(file => {
37+
const { camelName, kebabName } = exports.fileToComponentName(prefix, file)
38+
return async
39+
? `window.customElements.define('${kebabName}', wrap(Vue, () => import('~root/${file}')))\n`
40+
: (
41+
`import ${camelName} from '~root/${file}'\n` +
42+
`window.customElements.define('${kebabName}', wrap(Vue, ${camelName}))\n`
43+
)
44+
}).join('\n')}`.trim()
45+
fs.writeFileSync(filePath, content)
46+
return filePath
47+
}

‎packages/@vue/cli-service/lib/config/base.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module.exports = (api, options) => {
22
api.chainWebpack(webpackConfig => {
33
const resolveLocal = require('../util/resolveLocal')
4-
const inlineLimit = process.env.VUE_CLI_INLINE_LIMIT || 1000
4+
const inlineLimit = 10000
55

66
webpackConfig
77
.context(api.service.context)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"dependencies": {
2424
"@vue/cli-overlay": "^3.0.0-alpha.7",
2525
"@vue/cli-shared-utils": "^3.0.0-alpha.7",
26-
"@vue/web-component-wrapper": "^1.1.2",
26+
"@vue/web-component-wrapper": "^1.2.0",
2727
"address": "^1.0.3",
2828
"autodll-webpack-plugin": "^0.3.8",
2929
"autoprefixer": "^7.2.5",

0 commit comments

Comments
 (0)
Please sign in to comment.