Commit c3d864f3 authored by 尚斌杰's avatar 尚斌杰

update

parent 550dc2f6
# vue-cli3多页开发插件
### 新建页面
npm run add [page]
### 构建页面
npm run build [page1+page2] or npm run build [page1]
\ No newline at end of file
const { const {
info info
} = require('@vue/cli-shared-utils') } = require('@vue/cli-shared-utils')
const ejs = require('ejs')
const path = require('path') const fs = require('fs')
const resolve = require('resolve') const copy = require('copy')
const chalk = require('chalk') const resolve = require('resolve')
const copy = require('copy') const path = require('path')
const baseDir = resolve.sync('vue-cli-plugin-multi-pages', { basedir: process.cwd() }) const chalk = require('chalk')
const source = path.resolve(path.dirname(baseDir), 'generator/template/**') const inquirer = require('inquirer')
const dist = 'src/pages/' const baseDir = resolve.sync('vue-cli-plugin-multi-pages', { basedir: process.cwd() })
const source = path.resolve(path.dirname(baseDir), 'generator/template/**')
module.exports = (api, options) => { module.exports = (api, opts)=>{
api.registerCommand('add', { api.registerCommand('add', {
description: 'add a new page', description: '创建一个新页面',
usage: 'vue-cli-service add [page]', usage: 'vue-cli-service add [page]',
}, async function serve (args) { }, async function serve (args) {
info('Adding a new page...') const entry = args._[0]
if(entry){
// entry arg const entryPath = api.resolve(`./src/pages/${entry}`)
const entry = args._[0] if( fs.existsSync(api.resolve(entryPath))){
// api.resolve(dist + entry) console.log(chalk.red(`${entry}页面已存在,请重新填写页面!`))
if (entry) { return
copy(source, api.resolve(dist + entry), function(err, files) { }
if (err) throw err; inquirer
// `files` is an array of the files that were copied .prompt([
console.log(chalk.yellow(`页面${entry}创建成功!`)) {
}); type: 'input',
} else { name: 'name',
console.log(chalk.red(`npm run add [page] 缺失参数: page`)) message: '请输入页面的中文名称(选填):'
} }
])
.then(answers => {
if(answers.name){
copy(source, api.resolve(entryPath), function(err, files) {
if (err) throw err;
// 修改html
modifyFile(`${entryPath}/main.html`,{title:answers.name},'string')
modifyFile(`${entryPath}/main.ts`,{name:entry})
modifyFile(`${entryPath}/views/Main.vue`,{title:answers.name})
console.log(chalk.green(`页面${entry}创建成功!`))
});
}
});
} else {
console.log(chalk.red(`npm run add [page] 缺少页面参数: page`))
}
/**
* 修改文件
* @param {*} file
* @param {*} data
*/
function modifyFile(file,data,type){
if (fs.existsSync(file)) {
const content = fs.readFileSync(file).toString();
let result = content;
if(type==='string'){
for (const dataItem of Object.keys(data)) {
const reg = new RegExp(`{{${dataItem}}}`,'g')
result = result.replace(reg,`${data[dataItem]}`);
}
} else{
result = ejs.compile(content)(data);
}
fs.writeFileSync(file, result);
}
}
}) })
} }
\ No newline at end of file
\ No newline at end of file
const glob = require('glob') const fs=require('fs-extra')
const path = require('path') const path = require('path')
const chalk = require('chalk')
const defaults = { const defaults = {
clean: true, clean: true,
target: 'app', target: 'app',
...@@ -12,7 +14,7 @@ const buildModes = { ...@@ -12,7 +14,7 @@ const buildModes = {
wc: 'web component', wc: 'web component',
'wc-async': 'web component (async)' 'wc-async': 'web component (async)'
} }
const allPages = fs.readdirSync(path.resolve('./src/pages/')).filter(page=>(page!=='.DS_Store' && page !=='common'))
const modifyConfig = (config, fn) => { const modifyConfig = (config, fn) => {
if (Array.isArray(config)) { if (Array.isArray(config)) {
config.forEach(c => fn(c)) config.forEach(c => fn(c))
...@@ -20,7 +22,7 @@ const modifyConfig = (config, fn) => { ...@@ -20,7 +22,7 @@ const modifyConfig = (config, fn) => {
fn(config) fn(config)
} }
} }
module.exports = (api, options) => { module.exports = (api, options) => {
api.registerCommand('build', { api.registerCommand('build', {
description: 'build for production', description: 'build for production',
...@@ -45,53 +47,51 @@ module.exports = (api, options) => { ...@@ -45,53 +47,51 @@ module.exports = (api, options) => {
args[key] = defaults[key] args[key] = defaults[key]
} }
} }
// 拿到所有页面
const allPages = glob.sync('./src/pages/*').map(m=>m.replace('./src/pages/', '').replace('/', '')).filter(item=> item!=='common');
const entry = args._[0]?args._[0].split('+'):''
let pages = []
if(entry && entry.length>0){
for (const item of entry) {
if(allPages.indexOf(item)>=0){
pages.push(item)
}
}
} else{
pages=allPages
}
pages.forEach(async item => { if(!args._[0]){
console.log(chalk.red(`npm run build [page] 缺少页面参数: page`))
return
}
const pageData = getPages(args)
if(pageData.length===0){
console.log(chalk.red(`你所编译的页面不存在`))
return
}
pageData.forEach(async item=>{
args.entry = item args.entry = item
if (args.target !== 'app') { if (args.target !== 'app') {
args.entry = args.entry || 'src/App.vue' args.entry = args.entry || 'src/App.vue'
} }
process.env.VUE_CLI_BUILD_TARGET = args.target process.env.VUE_CLI_BUILD_TARGET = args.target
if (args.modern && args.target === 'app') { if (args.modern && args.target === 'app') {
process.env.VUE_CLI_MODERN_MODE = true process.env.VUE_CLI_MODERN_MODE = true
if (!process.env.VUE_CLI_MODERN_BUILD) { delete process.env.VUE_CLI_MODERN_BUILD
// if (!process.env.VUE_CLI_MODERN_BUILD) {
// main-process for legacy build // main-process for legacy build
await build(Object.assign({}, args, { await build(Object.assign({}, args, {
modernBuild: false, modernBuild: false,
keepAlive: true keepAlive: true
}), api, options) }), api, options)
// spawn sub-process of self for modern build // spawn sub-process of self for modern build
const { execa } = require('@vue/cli-shared-utils') // const { execa } = require('@vue/cli-shared-utils')
const cliBin = require('path').resolve(__dirname, '../../../bin/vue-cli-service.js') // const cliBin = require('@vue/cli-service/bin/vue-cli-service.js')
await execa(cliBin, ['build', ...rawArgs], { // await execa(cliBin, ['build', ...rawArgs], {
stdio: 'inherit', // stdio: 'inherit',
env: { // env: {
VUE_CLI_MODERN_BUILD: true // VUE_CLI_MODERN_BUILD: true
} // }
}) // })
} else { // } else {
process.env.VUE_CLI_MODERN_BUILD = true
// sub-process for modern build // sub-process for modern build
await build(Object.assign({}, args, { await build(Object.assign({}, args, {
modernBuild: true, modernBuild: true,
clean: false clean: false
}), api, options) }), api, options)
} // }
delete process.env.VUE_CLI_MODERN_MODE delete process.env.VUE_CLI_MODERN_MODE
delete process.env.VUE_CLI_MODERN_BUILD
} else { } else {
if (args.modern) { if (args.modern) {
const { warn } = require('@vue/cli-shared-utils') const { warn } = require('@vue/cli-shared-utils')
...@@ -104,13 +104,27 @@ module.exports = (api, options) => { ...@@ -104,13 +104,27 @@ module.exports = (api, options) => {
await build(args, api, options) await build(args, api, options)
} }
delete process.env.VUE_CLI_BUILD_TARGET delete process.env.VUE_CLI_BUILD_TARGET
})
}) })
})
}
/**
* 获取页面
* @param {Array} args
*/
function getPages(args) {
let pages = []
if(args._[0]){
args._[0].split('+').forEach(item=>{
if(allPages.indexOf(item)>=0){
pages.push(item)
}
})
}
return pages
} }
async function build (args, api, options) { async function build (args, api, options) {
const fs = require('fs-extra')
const path = require('path')
const chalk = require('chalk') const chalk = require('chalk')
const webpack = require('webpack') const webpack = require('webpack')
const formatStats = require('../util/formatStats') const formatStats = require('../util/formatStats')
...@@ -148,30 +162,49 @@ async function build (args, api, options) { ...@@ -148,30 +162,49 @@ async function build (args, api, options) {
// resolve raw webpack config // resolve raw webpack config
let webpackConfig let webpackConfig
if (args.target === 'lib') { if (args.target === 'lib') {
webpackConfig = require('./resolveLibConfig')(api, args, options) webpackConfig = require('../util/resolveLibConfig')(api, args, options)
} else if ( } else if (
args.target === 'wc' || args.target === 'wc' ||
args.target === 'wc-async' args.target === 'wc-async'
) { ) {
webpackConfig = require('./resolveWcConfig')(api, args, options) webpackConfig = require('../util/resolveWcConfig')(api, args, options)
} else { } else {
webpackConfig = require('../util/resolveAppConfig')(api, args, options) webpackConfig = require('../util/resolveAppConfig')(api, args, options)
} }
// check for common config errors // check for common config errors
validateWebpackConfig(webpackConfig, api, options, args.target) validateWebpackConfig(webpackConfig, api, options, args.target)
// 获取页眉页脚
const headerPath = path.resolve('./src/pages/common/header.html')
const footerPath = path.resolve('./src/pages/common/footer.html')
const headerTpl = fs.existsSync(headerPath)?fs.readFileSync(headerPath):''
const footerTpl = fs.existsSync(footerPath)?fs.readFileSync(footerPath):''
const entry = args.entry // 1 const entry = args.entry
if (entry) { if (entry) {
const pagesPath = "./src/pages/" + entry // 1,增加模块路径 const pagePath = "./src/pages/" + entry
webpackConfig.entry = { webpackConfig.entry={}
// app: api.resolve(entry) webpackConfig.entry[entry] = pagePath + '/main.ts'
app: pagesPath + '/main' // 2,修改entry入口
}
webpackConfig.output.publicPath = '' webpackConfig.output.publicPath = ''
webpackConfig.output.path = targetDir webpackConfig.output.path = targetDir
for(let item of webpackConfig.plugins) { for(let item of webpackConfig.plugins) {
if (item.options && item.options.template) item.options.template= pagesPath + '/main.html' // 3,修改webpackConfig.plugins.HtmlWebpackPlugin template 入口 if (item.options && item.options.template) {
item.options.template= pagePath + '/main.html'
item.options.header = headerTpl
item.options.footer = footerTpl
}
}
for (let item of webpackConfig.module.rules) {
if(item.test.toString().indexOf('png|jp')>=0){
for (const itemChild of item.use) {
if(itemChild.options){
itemChild.options.publicPath = itemChild.options.publicPath.indexOf(entry)<0?itemChild.options.publicPath+'/'+entry:itemChild.options.publicPath
break
}
}
break
}
} }
} }
...@@ -191,7 +224,7 @@ async function build (args, api, options) { ...@@ -191,7 +224,7 @@ async function build (args, api, options) {
// Expose advanced stats // Expose advanced stats
if (args.dashboard) { if (args.dashboard) {
const DashboardPlugin = require('../../webpack/DashboardPlugin') const DashboardPlugin = require('@vue/cli-service/lib/webpack/DashboardPlugin')
modifyConfig(webpackConfig, config => { modifyConfig(webpackConfig, config => {
config.plugins.push(new DashboardPlugin({ config.plugins.push(new DashboardPlugin({
type: 'build', type: 'build',
...@@ -219,7 +252,7 @@ async function build (args, api, options) { ...@@ -219,7 +252,7 @@ async function build (args, api, options) {
} }
if (args.clean) { if (args.clean) {
await fs.remove(targetDir) await fs.remove(api.resolve(args.dest || options.outputDir))
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
...@@ -262,3 +295,4 @@ async function build (args, api, options) { ...@@ -262,3 +295,4 @@ async function build (args, api, options) {
module.exports.defaultModes = { module.exports.defaultModes = {
build: 'production' build: 'production'
} }
\ No newline at end of file
This diff is collapsed.
module.exports = (api, options, rootOptions) => { module.exports=(api,opts)=>{
// 修改 `package.json` 里的字段
api.extendPackage({ api.extendPackage({
scripts: { scripts: {
start: 'vue-cli-service serve', add: 'vue-cli-service add',
build: 'vue-cli-service build', },
add: 'vue-cli-service add' devDependencies:{
} "inquirer": "^7.0.0",
"copy": "^0.3.2",
},
}) })
}
// 复制并用 ejs 渲染 `./template` 内所有的文件 \ No newline at end of file
api.render('./template', {
doesCompile: api.hasPlugin('babel') || api.hasPlugin('typescript')
})
// if (options.foo) {
// // 有条件地生成文件
// }
// 根据eslint配置自动校正
api.onCreateComplete(() => {
// Linting the generated files
if (api.hasPlugin('eslint')) {
// Lint generated/modified files
try {
const lint = require('@vue/cli-plugin-eslint/lint')
lint({ silent: true }, api)
} catch (e) {
// No ESLint vue-cli plugin
}
}
})
}
\ No newline at end of file
<%_ if (!rootOptions.router) { _%>
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<%_ if (!rootOptions.bare) { _%>
<HelloWorld msg="Welcome to Your Vue.js App"/>
<%_ } else { _%>
<h1>Welcome to Your Vue.js App</h1>
<%_ } _%>
</div>
</template>
<%_ if (!rootOptions.bare) { _%>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
components: {
HelloWorld
}
}
</script>
<%_ } _%>
<%_ } else { _%>
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
<%_ } _%>
<%_ if (!rootOptions.bare) { _%>
<%_ if (rootOptions.cssPreprocessor !== 'stylus') { _%>
<style<%-
rootOptions.cssPreprocessor
? ` lang="${
rootOptions.cssPreprocessor === 'sass'
? 'scss'
: rootOptions.cssPreprocessor
}"`
: ``
%>>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
<%_ if (!rootOptions.router) { _%>
margin-top: 60px;
<%_ } _%>
}
<%_ if (rootOptions.router) { _%>
<%_ if (!rootOptions.cssPreprocessor) { _%>
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
<%_ } else { _%>
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
<%_ } _%>
<%_ } _%>
</style>
<%_ } else { _%>
<style lang="stylus">
#app
font-family 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color #2c3e50
<%_ if (!rootOptions.router) { _%>
margin-top 60px
<%_ } _%>
<%_ if (rootOptions.router) { _%>
#nav
padding 30px
a
font-weight bold
color #2c3e50
&.router-link-exact-active
color #42b983
<%_ } _%>
</style>
<%_ } _%>
<%_ } _%>
<%_ if (!rootOptions.bare) { _%>
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<%_ for (plugin of plugins) { _%>
<li><a href="<%- plugin.link %>" target="_blank" rel="noopener"><%- plugin.name %></a></li>
<%_ } _%>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<%_ if (rootOptions.cssPreprocessor !== 'stylus') { _%>
<style scoped<%-
rootOptions.cssPreprocessor
? ` lang="${
rootOptions.cssPreprocessor === 'sass'
? 'scss'
: rootOptions.cssPreprocessor
}"`
: ``
%>>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
<%_ } else { _%>
<style scoped lang="stylus">
h3
margin 40px 0 0
ul
list-style-type none
padding 0
li
display inline-block
margin 0 10px
a
color #42b983
</style>
<%_ } _%>
<%_ } _%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%%= BASE_URL %%>favicon.ico">
<title><%= rootOptions.projectName %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= rootOptions.projectName %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
<%_ if (doesCompile) { _%>
render: h => h(App),
<%_ } else { _%>
render: function (h) { return h(App) },
<%_ } _%>
}).$mount('#app')
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<%= htmlWebpackPlugin.options.header %> <%= htmlWebpackPlugin.options.header %>
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>标题</title> <title>{{title}}</title>
<script> <script>
</script> </script>
</head> </head>
......
...@@ -5,13 +5,15 @@ import VueAxios from 'vue-axios'; ...@@ -5,13 +5,15 @@ import VueAxios from 'vue-axios';
import '@/utils/common'; import '@/utils/common';
const win = window as any; const win = window as any;
// 全局设置cors cookie
axios.defaults.withCredentials = true; axios.defaults.withCredentials = true;
axios.defaults.headers['Content-Type'] = 'application/json'; axios.defaults.headers['Content-Type'] = 'application/json';
Vue.use(VueAxios, axios); Vue.use(VueAxios, axios);
// 阻止启动生产消息
Vue.config.productionTip = false; Vue.config.productionTip = false;
new Vue({ new Vue({
name: 'module', name: '<%= name %>',
components: { components: {
Main, Main,
}, },
......
<template> <template>
<div> <div class="container">
<HeaderBar>title</HeaderBar> <HeaderBar><%= title %></HeaderBar>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import HeaderBar from '@/components/headerBar/index.vue'; import HeaderBar from '@/components/headerBar/index.vue';
@Component({ @Component({
components: { components: {
HeaderBar, HeaderBar,
}, },
}) })
export default class Home extends Vue { export default class Main extends Vue {
// 生命周期钩子
public created() {
console.log('created')
}
private mounted() {
console.log('mounted')
}
} }
</script> </script>
......
const add = require('./commands/add')
const build = require('./commands/build') const build = require('./commands/build')
const serve = require('./commands/serve') const serve = require('./commands/serve')
const add = require('./commands/add') module.exports = (api, opts) =>{
module.exports = (api, opts) => { add(api, opts)
build(api, opts) build(api, opts)
serve(api,opts) serve(api, opts)
add(api,opts)
} }
\ No newline at end of file
{ {
"name": "vue-cli-plugin-multi-pages", "name": "vue-cli-plugin-multi-pages",
"version": "1.0.0", "version": "1.1.3",
"description": "", "description": "vue-cli3多页开发插件",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "", "keywords": [],
"license": "ISC", "author": "shangbj",
"devDependencies": { "license": "ISC"
"copy": "^0.3.2" }
}
}
\ No newline at end of file
...@@ -4,7 +4,6 @@ module.exports = function formatStats (stats, dir, api) { ...@@ -4,7 +4,6 @@ module.exports = function formatStats (stats, dir, api) {
const zlib = require('zlib') const zlib = require('zlib')
const chalk = require('chalk') const chalk = require('chalk')
const ui = require('cliui')({ width: 80 }) const ui = require('cliui')({ width: 80 })
const url = require('url');
const json = stats.toJson({ const json = stats.toJson({
hash: false, hash: false,
...@@ -21,10 +20,6 @@ module.exports = function formatStats (stats, dir, api) { ...@@ -21,10 +20,6 @@ module.exports = function formatStats (stats, dir, api) {
const isCSS = val => /\.css$/.test(val) const isCSS = val => /\.css$/.test(val)
const isMinJS = val => /\.min\.js$/.test(val) const isMinJS = val => /\.min\.js$/.test(val)
assets = assets assets = assets
.map(a => {
a.name = url.parse(a.name).pathname
return a
})
.filter(a => { .filter(a => {
if (seenNames.has(a.name)) { if (seenNames.has(a.name)) {
return false return false
......
const path = require('path')
module.exports = function getAssetPath (options, filePath) {
return options.assetsDir
? path.posix.join(options.assetsDir, filePath)
: filePath
}
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
...@@ -31,7 +31,7 @@ module.exports = (api, args, options) => { ...@@ -31,7 +31,7 @@ module.exports = (api, args, options) => {
unsafeInline: args['unsafe-inline'], unsafeInline: args['unsafe-inline'],
// as we may generate an addition file asset (if `no-unsafe-inline` specified) // as we may generate an addition file asset (if `no-unsafe-inline` specified)
// we need to provide the correct directory for that file to place in // we need to provide the correct directory for that file to place in
jsDirectory: require('vue-cli-plugin-multi-pages/util/getAssetPath')(options, 'js') jsDirectory: require('../../util/getAssetPath')(options, 'js')
}]) }])
} }
} }
...@@ -42,6 +42,7 @@ module.exports = (api, args, options) => { ...@@ -42,6 +42,7 @@ module.exports = (api, args, options) => {
if (args.entry && !options.pages) { if (args.entry && !options.pages) {
rawConfig.entry = { app: api.resolve(args.entry) } rawConfig.entry = { app: api.resolve(args.entry) }
} }
return rawConfig return rawConfig
} }
\ No newline at end of file
const fs = require('fs')
const path = require('path')
module.exports = (api, { entry, name, formats, filename }, options) => {
const { log, error } = require('@vue/cli-shared-utils')
const abort = msg => {
log()
error(msg)
process.exit(1)
}
const fullEntryPath = api.resolve(entry)
if (!fs.existsSync(fullEntryPath)) {
abort(
`Failed to resolve lib entry: ${entry}${entry === `src/App.vue` ? ' (default)' : ''}. ` +
`Make sure to specify the correct entry file.`
)
}
const isVueEntry = /\.vue$/.test(entry)
const libName = (
name ||
api.service.pkg.name ||
path.basename(entry).replace(/\.(jsx?|vue)$/, '')
)
filename = filename || libName
function genConfig (format, postfix = format, genHTML) {
const config = api.resolveChainableWebpackConfig()
const browserslist = require('browserslist')
const targets = browserslist(undefined, { path: fullEntryPath })
const supportsIE = targets.some(agent => agent.includes('ie'))
const webpack = require('webpack')
config.plugin('need-current-script-polyfill')
.use(webpack.DefinePlugin, [{
'process.env.NEED_CURRENTSCRIPT_POLYFILL': JSON.stringify(supportsIE)
}])
// adjust css output name so they write to the same file
if (config.plugins.has('extract-css')) {
config
.plugin('extract-css')
.tap(args => {
args[0].filename = `${filename}.css`
return args
})
}
// only minify min entry
if (!/\.min/.test(postfix)) {
config.optimization.minimize(false)
}
// externalize Vue in case user imports it
config
.externals({
...config.get('externals'),
vue: {
commonjs: 'vue',
commonjs2: 'vue',
root: 'Vue'
}
})
// inject demo page for umd
if (genHTML) {
const template = isVueEntry ? 'demo-lib.html' : 'demo-lib-js.html'
config
.plugin('demo-html')
.use(require('html-webpack-plugin'), [{
template: path.resolve(__dirname, template),
inject: false,
filename: 'demo.html',
libName,
assetsFileName: filename,
cssExtract: config.plugins.has('extract-css')
}])
}
// resolve entry/output
const entryName = `${filename}.${postfix}`
config.resolve
.alias
.set('~entry', fullEntryPath)
// set output target before user configureWebpack hooks are applied
config.output.libraryTarget(format)
// set entry/output after user configureWebpack hooks are applied
const rawConfig = api.resolveWebpackConfig(config)
let realEntry = require.resolve('./entry-lib.js')
// avoid importing default if user entry file does not have default export
if (!isVueEntry) {
const entryContent = fs.readFileSync(fullEntryPath, 'utf-8')
if (!/\b(export\s+default|export\s{[^}]+as\s+default)\b/.test(entryContent)) {
realEntry = require.resolve('./entry-lib-no-default.js')
}
}
rawConfig.entry = {
[entryName]: realEntry
}
rawConfig.output = Object.assign({
library: libName,
libraryExport: isVueEntry ? 'default' : undefined,
libraryTarget: format,
// preserve UDM header from webpack 3 until webpack provides either
// libraryTarget: 'esm' or target: 'universal'
// https://github.com/webpack/webpack/issues/6522
// https://github.com/webpack/webpack/issues/6525
globalObject: `(typeof self !== 'undefined' ? self : this)`
}, rawConfig.output, {
filename: `${entryName}.js`,
chunkFilename: `${entryName}.[name].js`,
// use dynamic publicPath so this can be deployed anywhere
// the actual path will be determined at runtime by checking
// document.currentScript.src.
publicPath: ''
})
return rawConfig
}
const configMap = {
commonjs: genConfig('commonjs2', 'common'),
umd: genConfig('umd', undefined, true),
'umd-min': genConfig('umd', 'umd.min')
}
const formatArray = (formats + '').split(',')
const configs = formatArray.map(format => configMap[format])
if (configs.indexOf(undefined) !== -1) {
const unknownFormats = formatArray.filter(f => configMap[f] === undefined).join(', ')
abort(
`Unknown library build formats: ${unknownFormats}`
)
}
return configs
}
const path = require('path')
const { resolveEntry, fileToComponentName } = require('./resolveWcEntry')
module.exports = (api, { target, entry, name }) => {
// Disable CSS extraction and turn on CSS shadow mode for vue-style-loader
process.env.VUE_CLI_CSS_SHADOW_MODE = true
const { log, error } = require('@vue/cli-shared-utils')
const abort = msg => {
log()
error(msg)
process.exit(1)
}
const isAsync = /async/.test(target)
// generate dynamic entry based on glob files
const resolvedFiles = require('globby').sync([entry], { cwd: api.resolve('.') })
if (!resolvedFiles.length) {
abort(`entry pattern "${entry}" did not match any files.`)
}
let libName
let prefix
if (resolvedFiles.length === 1) {
// in single mode, determine the lib name from filename
libName = name || fileToComponentName('', resolvedFiles[0]).kebabName
prefix = ''
if (libName.indexOf('-') < 0) {
abort(`--name must contain a hyphen when building a single web component.`)
}
} else {
// multi mode
libName = prefix = (name || api.service.pkg.name)
if (!libName) {
abort(`--name is required when building multiple web components.`)
}
}
const dynamicEntry = resolveEntry(prefix, libName, resolvedFiles, isAsync)
function genConfig (minify, genHTML) {
const config = api.resolveChainableWebpackConfig()
// make sure not to transpile wc-wrapper
config.module
.rule('js')
.exclude
.add(/vue-wc-wrapper/)
// only minify min entry
if (!minify) {
config.optimization.minimize(false)
}
// externalize Vue in case user imports it
config
.externals({
...config.get('externals'),
vue: 'Vue'
})
config
.plugin('web-component-options')
.use(require('webpack/lib/DefinePlugin'), [{
'process.env': {
CUSTOM_ELEMENT_NAME: JSON.stringify(libName)
}
}])
// enable shadow mode in vue-loader
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
options.shadowMode = true
return options
})
if (genHTML) {
config
.plugin('demo-html')
.use(require('html-webpack-plugin'), [{
template: path.resolve(__dirname, `./demo-wc.html`),
inject: false,
filename: 'demo.html',
libName,
components:
prefix === ''
? [libName]
: resolvedFiles.map(file => {
return fileToComponentName(prefix, file).kebabName
})
}])
}
// set entry/output last so it takes higher priority than user
// configureWebpack hooks
// set proxy entry for *.vue files
config.resolve
.alias
.set('~root', api.resolve('.'))
const rawConfig = api.resolveWebpackConfig(config)
const entryName = `${libName}${minify ? `.min` : ``}`
rawConfig.entry = {
[entryName]: dynamicEntry
}
Object.assign(rawConfig.output, {
// to ensure that multiple copies of async wc bundles can co-exist
// on the same page.
jsonpFunction: libName.replace(/-\w/g, c => c.charAt(1).toUpperCase()) + '_jsonp',
filename: `${entryName}.js`,
chunkFilename: `${libName}.[name]${minify ? `.min` : ``}.js`,
// use dynamic publicPath so this can be deployed anywhere
// the actual path will be determined at runtime by checking
// document.currentScript.src.
publicPath: ''
})
return rawConfig
}
return [
genConfig(false, true),
genConfig(true, false)
]
}
File mode changed from 100644 to 100755
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment