feat: complete --target wc & multi-wc + tests

This commit is contained in:
Evan You
2018-02-02 00:56:27 -05:00
parent 4f095f0791
commit 9a07eebea5
19 changed files with 327 additions and 58 deletions

View File

@@ -0,0 +1,13 @@
<template>
<h1>{{ msg }}</h1>
</template>
<script>
export default {
data: () => ({ msg: 'hi' })
}
</script>
<style>
h1 { color: red }
</style>

View File

@@ -14,19 +14,7 @@ const binPath = require.resolve('@vue/cli/bin/vue')
const sleep = n => new Promise(resolve => setTimeout(resolve, n))
const write = (file, content) => fs.writeFileSync(path.join(cwd, file), content)
const entryVue = `
<template>
<h1>{{ msg }}</h1>
</template>
<script>
export default {
data: () => ({ msg: 'hi' })
}
</script>
<style>
h1 { color: red }
</style>
`.trim()
const entryVue = fs.readFileSync(path.resolve(__dirname, 'entry.vue'), 'utf-8')
const entryJs = `
import Vue from 'vue'

View File

@@ -0,0 +1,60 @@
jest.setTimeout(20000)
const fs = require('fs')
const path = require('path')
const portfinder = require('portfinder')
const { createServer } = require('http-server')
const mkdirp = require('mkdirp')
const execa = require('execa')
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')
const cwd = path.resolve(__dirname, 'temp')
const binPath = require.resolve('@vue/cli/bin/vue')
const write = (file, content) => fs.writeFileSync(path.join(cwd, file), content)
const entryVue = fs.readFileSync(path.resolve(__dirname, 'entry.vue'), 'utf-8')
beforeAll(() => {
mkdirp.sync(cwd)
write('testLib.vue', entryVue)
})
let server, browser, page
test('global build --target lib', async () => {
const { stdout } = await execa(binPath, ['build', 'testLib.vue', '--target', 'lib'], { cwd })
expect(stdout).toMatch('Build complete.')
const distDir = path.join(cwd, 'dist')
const hasFile = file => fs.existsSync(path.join(distDir, file))
expect(hasFile('demo.html')).toBe(true)
expect(hasFile('testLib.common.js')).toBe(true)
expect(hasFile('testLib.umd.js')).toBe(true)
expect(hasFile('testLib.umd.min.js')).toBe(true)
expect(hasFile('testLib.css')).toBe(true)
const port = await portfinder.getPortPromise()
server = createServer({ root: distDir })
await new Promise((resolve, reject) => {
server.listen(port, err => {
if (err) return reject(err)
resolve()
})
})
const launched = await launchPuppeteer(`http://localhost:${port}/demo.html`)
browser = launched.browser
page = launched.page
const h1Text = await page.evaluate(() => {
return document.querySelector('h1').textContent
})
expect(h1Text).toMatch('hi')
})
afterAll(async () => {
await browser.close()
server.close()
})

View File

@@ -0,0 +1,58 @@
jest.setTimeout(20000)
const fs = require('fs')
const path = require('path')
const portfinder = require('portfinder')
const { createServer } = require('http-server')
const mkdirp = require('mkdirp')
const execa = require('execa')
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')
const cwd = path.resolve(__dirname, 'temp')
const binPath = require.resolve('@vue/cli/bin/vue')
const write = (file, content) => fs.writeFileSync(path.join(cwd, file), content)
const entryVue = fs.readFileSync(path.resolve(__dirname, 'entry.vue'), 'utf-8')
beforeAll(() => {
mkdirp.sync(cwd)
write('my-wc.vue', entryVue)
})
let server, browser, page
test('global build --target wc', async () => {
const { stdout } = await execa(binPath, ['build', 'my-wc.vue', '--target', 'wc'], { cwd })
expect(stdout).toMatch('Build complete.')
const distDir = path.join(cwd, 'dist')
const hasFile = file => fs.existsSync(path.join(distDir, file))
expect(hasFile('demo.html')).toBe(true)
expect(hasFile('my-wc.js')).toBe(true)
expect(hasFile('my-wc.min.js')).toBe(true)
const port = await portfinder.getPortPromise()
server = createServer({ root: distDir })
await new Promise((resolve, reject) => {
server.listen(port, err => {
if (err) return reject(err)
resolve()
})
})
const launched = await launchPuppeteer(`http://localhost:${port}/demo.html`)
browser = launched.browser
page = launched.page
const h1Text = await page.evaluate(() => {
return document.querySelector('my-wc')._shadowRoot.querySelector('h1').textContent
})
expect(h1Text).toMatch('hi')
})
afterAll(async () => {
await browser.close()
server.close()
})

View File

@@ -6,7 +6,7 @@ const { toPlugin, findExisting } = require('./lib/util')
const babelPlugin = toPlugin('@vue/cli-plugin-babel')
const eslintPlugin = toPlugin('@vue/cli-plugin-eslint')
const createConfigPlugin = require('./lib/createConfigPlugin')
const globalConfigPlugin = require('./lib/globalConfigPlugin')
function resolveEntry (entry) {
const context = process.cwd()
@@ -44,7 +44,7 @@ function createService (context, entry, asLib) {
plugins: [
babelPlugin,
eslintPlugin,
createConfigPlugin(context, entry, asLib)
globalConfigPlugin(context, entry, asLib)
]
})
}

View File

@@ -71,7 +71,8 @@ module.exports = function createConfigPlugin (context, entry, asLib) {
.clear()
.end()
.exclude
.add(/node_modules|@vue\/cli-service/)
.add(/node_modules/)
.add(/@vue\/cli-service/)
.end()
.uses
.delete('cache-loader')

View File

@@ -9,25 +9,16 @@ const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')
let server, browser, page
test('build as lib', async () => {
const project = await create('e2e-build-lib', defaultPreset)
const project = await create('build-lib', defaultPreset)
const { stdout } = await project.run('vue-cli-service build --traget lib --name testLib')
const { stdout } = await project.run('vue-cli-service build --target lib --name testLib src/components/HelloWorld.vue')
expect(stdout).toMatch('Build complete.')
expect(project.has('dist/index.html')).toBe(true)
expect(project.has('dist/favicon.ico')).toBe(true)
expect(project.has('dist/js')).toBe(true)
expect(project.has('dist/css')).toBe(true)
const index = await project.read('dist/index.html')
// should preload app.js & vendor.js
expect(index).toMatch(/<link rel=preload [^>]+app[^>]+\.js>/)
expect(index).toMatch(/<link rel=preload [^>]+vendor[^>]+\.js>/)
const vendorFile = index.match(/<link rel=preload [^>]+(vendor[^>]+\.js)>/)[1]
const vendor = await project.read(`dist/js/${vendorFile}`)
expect(vendor).toMatch(`router-link`)
expect(vendor).toMatch(`vuex`)
expect(project.has('dist/demo.html')).toBe(true)
expect(project.has('dist/testLib.common.js')).toBe(true)
expect(project.has('dist/testLib.umd.js')).toBe(true)
expect(project.has('dist/testLib.umd.min.js')).toBe(true)
expect(project.has('dist/testLib.css')).toBe(true)
const port = await portfinder.getPortPromise()
server = createServer({ root: path.join(project.dir, 'dist') })
@@ -39,15 +30,19 @@ test('build as lib', async () => {
})
})
const launched = await launchPuppeteer(`http://localhost:${port}/`)
const launched = await launchPuppeteer(`http://localhost:${port}/demo.html`)
browser = launched.browser
page = launched.page
const h1Text = await page.evaluate(() => {
return document.querySelector('h1').textContent
})
expect(h1Text).toMatch('') // no props given
expect(h1Text).toMatch('Welcome to Your Vue.js App')
const h2Text = await page.evaluate(() => {
return document.querySelector('h2').textContent
})
expect(h2Text).toMatch('Essential Links')
})
afterAll(async () => {

View File

@@ -0,0 +1,59 @@
jest.setTimeout(30000)
const path = require('path')
const portfinder = require('portfinder')
const { createServer } = require('http-server')
const { defaultPreset } = require('@vue/cli/lib/options')
const create = require('@vue/cli-test-utils/createTestProject')
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')
let server, browser, page
test('build as single wc', async () => {
const project = await create('build-multi-wc', defaultPreset)
const { stdout } = await project.run(`vue-cli-service build --target multi-wc **/*.vue`)
expect(stdout).toMatch('Build complete.')
expect(project.has('dist/demo.html')).toBe(true)
expect(project.has('dist/build-multi-wc.js')).toBe(true)
expect(project.has('dist/build-multi-wc.min.js')).toBe(true)
const port = await portfinder.getPortPromise()
server = createServer({ root: path.join(project.dir, 'dist') })
await new Promise((resolve, reject) => {
server.listen(port, err => {
if (err) return reject(err)
resolve()
})
})
const launched = await launchPuppeteer(`http://localhost:${port}/demo.html`)
browser = launched.browser
page = launched.page
const styleCount = await page.evaluate(() => {
return document.querySelector('build-multi-wc-app')._shadowRoot.querySelectorAll('style').length
})
expect(styleCount).toBe(2) // should contain styles from both app and child
const h1Text = await page.evaluate(() => {
return document.querySelector('build-multi-wc-app')._shadowRoot.querySelector('h1').textContent
})
expect(h1Text).toMatch('Welcome to Your Vue.js App')
const childStyleCount = await page.evaluate(() => {
return document.querySelector('build-multi-wc-hello-world')._shadowRoot.querySelectorAll('style').length
})
expect(childStyleCount).toBe(1)
const h2Text = await page.evaluate(() => {
return document.querySelector('build-multi-wc-hello-world')._shadowRoot.querySelector('h2').textContent
})
expect(h2Text).toMatch('Essential Links')
})
afterAll(async () => {
await browser.close()
server.close()
})

View File

@@ -0,0 +1,52 @@
jest.setTimeout(30000)
const path = require('path')
const portfinder = require('portfinder')
const { createServer } = require('http-server')
const { defaultPreset } = require('@vue/cli/lib/options')
const create = require('@vue/cli-test-utils/createTestProject')
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')
let server, browser, page
test('build as single wc', async () => {
const project = await create('build-wc', defaultPreset)
const { stdout } = await project.run('vue-cli-service build --target wc src/components/HelloWorld.vue')
expect(stdout).toMatch('Build complete.')
expect(project.has('dist/demo.html')).toBe(true)
expect(project.has('dist/build-wc.js')).toBe(true)
expect(project.has('dist/build-wc.min.js')).toBe(true)
const port = await portfinder.getPortPromise()
server = createServer({ root: path.join(project.dir, 'dist') })
await new Promise((resolve, reject) => {
server.listen(port, err => {
if (err) return reject(err)
resolve()
})
})
const launched = await launchPuppeteer(`http://localhost:${port}/demo.html`)
browser = launched.browser
page = launched.page
const styleCount = await page.evaluate(() => {
return document.querySelector('build-wc')._shadowRoot.querySelectorAll('style').length
})
expect(styleCount).toBe(1)
await page.evaluate(() => {
document.querySelector('build-wc').msg = 'hello'
})
const h1Text = await page.evaluate(() => {
return document.querySelector('build-wc')._shadowRoot.querySelector('h1').textContent
})
expect(h1Text).toBe('hello')
})
afterAll(async () => {
await browser.close()
server.close()
})

View File

@@ -0,0 +1,6 @@
<title><%- htmlWebpackPlugin.options.libName %> demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="./<%- htmlWebpackPlugin.options.libName %>.js"></script>
<% for (const comp of htmlWebpackPlugin.options.components) { %>
<<%= comp %>></<%= comp %>>
<% } %>

View File

@@ -11,19 +11,30 @@ const hyphenate = str => {
return str.replace(hyphenateRE, '-$1').toLowerCase()
}
module.exports = function generateMultiWebComponentEntry (prefix, files) {
exports.filesToComponentNames = (prefix, files) => {
return files.map(file => {
const basename = path.basename(file).replace(/\.(jsx?|vue)$/, '')
const camelName = camelize(basename)
const kebabName = `${prefix}-${hyphenate(basename)}`
return {
file,
basename,
camelName,
kebabName
}
})
}
exports.generateMultiWebComponentEntry = (prefix, files) => {
const filePath = path.resolve(__dirname, 'entry-multi-wc.js')
const content = `
import Vue from 'vue'
import wrap from '@vue/web-component-wrapper'
${files.map(file => {
const basename = path.basename(file).replace(/\.(jsx?|vue)$/, '')
const camelName = camelize(basename)
const kebabName = hyphenate(basename)
${exports.filesToComponentNames(prefix, files).map(({ file, camelName, kebabName }) => {
return (
`import ${camelName} from '~root/${file}'\n` +
`window.customElements.define('${prefix}-${kebabName}', wrap(Vue, ${camelName}))\n`
`window.customElements.define('${kebabName}', wrap(Vue, ${camelName}))\n`
)
}).join('\n')}`.trim()
fs.writeFileSync(filePath, content)

View File

@@ -15,9 +15,11 @@ module.exports = (api, options) => {
'--name': `name for lib or (multi-)web-component mode (default: "name" in package.json or entry filename)`
}
}, args => {
args.entry = args._[0]
args.entry = args.entry || args._[0]
for (const key in defaults) {
if (args[key] == null) args[key] = defaults[key]
if (args[key] == null) {
args[key] = defaults[key]
}
}
if (args.dest == null) {
args.dest = options.outputDir

View File

@@ -1,7 +1,11 @@
const path = require('path')
module.exports = (api, { entry, name, dest }, options) => {
const libName = name || api.service.pkg.name || entry.replace(/\.(js|vue)$/, '')
const libName = (
name ||
api.service.pkg.name ||
path.basename(entry).replace(/\.(jsx?|vue)$/, '')
)
// setting this disables app-only configs
process.env.VUE_CLI_TARGET = 'lib'
// inline all static asset files since there is no publicPath handling

View File

@@ -1,4 +1,8 @@
const path = require('path')
const {
filesToComponentNames,
generateMultiWebComponentEntry
} = require('./generateMultiWcEntry')
module.exports = (api, { target, entry, name, dest, prefix }) => {
const { log, error } = require('@vue/cli-shared-utils')
@@ -8,22 +12,27 @@ module.exports = (api, { target, entry, name, dest, prefix }) => {
process.exit(1)
}
const libName = name || api.service.pkg.name || entry.replace(/\.(js|vue)$/, '')
const libName = (
name ||
api.service.pkg.name ||
path.basename(entry).replace(/\.(jsx?|vue)$/, '')
)
if (libName.indexOf('-') < 0 && target !== 'multi-wc') {
abort(`--name must contain a hyphen with --target web-component. (got "${libName}")`)
}
let dynamicEntry
let resolvedFiles
if (target === 'multi-wc') {
if (!entry) {
abort(`a glob pattern is required with --target multi-web-component.`)
}
// generate dynamic entry based on glob files
const files = require('globby').sync([entry], { cwd: api.resolve('.') })
if (!files.length) {
resolvedFiles = require('globby').sync([entry], { cwd: api.resolve('.') })
if (!resolvedFiles.length) {
abort(`glob pattern "${entry}" did not match any files.`)
}
dynamicEntry = require('./generateMultiWcEntry')(libName, files)
dynamicEntry = generateMultiWebComponentEntry(libName, resolvedFiles)
}
// setting this disables app-only configs
@@ -55,6 +64,12 @@ module.exports = (api, { target, entry, name, dest, prefix }) => {
.set('~entry', api.resolve(entry))
}
// make sure not to transpile wc-wrapper
config.module
.rule('js')
.exclude
.add(/vue-wc-wrapper/)
// only minify min entry
if (!minify) {
config.plugins.delete('uglify')
@@ -91,10 +106,13 @@ module.exports = (api, { target, entry, name, dest, prefix }) => {
config
.plugin('demo-html')
.use(require('html-webpack-plugin'), [{
template: path.resolve(__dirname, './demo-wc.html'),
template: path.resolve(__dirname, `./demo-${target}.html`),
inject: false,
filename: 'demo.html',
libName
libName,
components: target === 'multi-wc'
? filesToComponentNames(libName, resolvedFiles).map(c => c.kebabName)
: null
}])
}

View File

@@ -23,7 +23,7 @@
"dependencies": {
"@vue/cli-overlay": "^3.0.0-alpha.5",
"@vue/cli-shared-utils": "^3.0.0-alpha.5",
"@vue/web-component-wrapper": "^1.0.0",
"@vue/web-component-wrapper": "^1.1.2",
"address": "^1.0.3",
"autodll-webpack-plugin": "^0.3.8",
"autoprefixer": "^7.2.5",
@@ -57,7 +57,7 @@
"thread-loader": "^1.1.2",
"uglifyjs-webpack-plugin": "^1.1.6",
"url-loader": "^0.6.2",
"vue-loader": "^14.1.0",
"vue-loader": "^14.1.1",
"vue-template-compiler": "^2.5.13",
"webpack": "^3.10.0",
"webpack-chain": "^4.5.0",

View File

@@ -127,7 +127,9 @@ function cleanArgs (cmd) {
const args = {}
cmd.options.forEach(o => {
const key = o.long.replace(/^--/, '')
args[key] = cmd[key]
if (typeof cmd[key] === 'string') {
args[key] = cmd[key]
}
})
return args
}

View File

@@ -713,9 +713,9 @@
dependencies:
lodash "^4.17.4"
"@vue/web-component-wrapper@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@vue/web-component-wrapper/-/web-component-wrapper-1.0.0.tgz#67b357616b38289836ae5f18bfb0095ba66b3fe4"
"@vue/web-component-wrapper@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@vue/web-component-wrapper/-/web-component-wrapper-1.1.2.tgz#9148b4655560bb5e77c0ab7d9836b98d5cd2ffb6"
JSONStream@^1.0.4:
version "1.3.2"
@@ -10081,9 +10081,9 @@ vue-jest@^2.0.0:
tsconfig "^7.0.0"
vue-template-es2015-compiler "^1.5.3"
vue-loader@^14.1.0:
version "14.1.0"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-14.1.0.tgz#ae31d62a11421061fca8bac30cbc15875df886d3"
vue-loader@^14.1.1:
version "14.1.1"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-14.1.1.tgz#331f197fcea790d6b8662c29b850806e7eb29342"
dependencies:
consolidate "^0.14.0"
hash-sum "^1.0.2"