diff --git a/README.md b/README.md index 81c23d9..4f637bf 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - 开发项目 `service:dev` - 打包项目 `service:build` - 启动项目 `service:start` -- 检查构建 `service:ci` +- storybook webpack扩展支持 `build-storybook` ### 命令参数 帮助 -h @@ -33,7 +33,7 @@ ### 技术栈 -- vue 2.5 +- vue 2.6 - vue-property-decorator - vue-router - vue-server-renderer @@ -43,9 +43,14 @@ - webpack 4 - express 4 - babel 7 +- storybook 5 ### 特性 +#### 内置支持 storybook + +- 方便开发组件 + #### 支持 SSR 使用 webpack4 打包 css 无法提取的问题 #### 支持 注入 Env diff --git a/bin/build b/bin/build old mode 100644 new mode 100755 diff --git a/bin/dev b/bin/dev old mode 100644 new mode 100755 diff --git a/bin/start b/bin/start old mode 100644 new mode 100755 diff --git a/bin/storybook b/bin/storybook new file mode 100644 index 0000000..ecfde7f --- /dev/null +++ b/bin/storybook @@ -0,0 +1,6 @@ +#!/usr/bin/env node +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const argv = require('./utils').getArgv() + +require('../dist/bootstrap-storybook').bootstrapStorybook(argv) \ No newline at end of file diff --git a/bin/utils.js b/bin/utils.js index dd6ab9f..f78a2ce 100644 --- a/bin/utils.js +++ b/bin/utils.js @@ -114,5 +114,5 @@ function getArgv() { } module.exports = { - getArgv + getArgv, } diff --git a/package.json b/package.json index d3883bc..79d8094 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,10 @@ "bin": { "service:start": "./bin/start", "service:build": "./bin/build", - "service:dev": "./bin/dev" + "service:dev": "./bin/dev", + "bootstrap:storybook": "./bin/storybook" }, + "main": "./dist/index.js", "typings": "@types/index.d.ts", "repository": { "type": "git", @@ -111,6 +113,7 @@ "babel-loader": "^8.0.4", "babel-plugin-component": "^1.1.1", "babel-preset-latest-node": "^2.0.0-beta.3", + "babel-preset-vue": "^2.0.2", "base64url": "^3.0.1", "compression": "^1.7.3", "consola": "2.2.4", diff --git a/src/bin/bootstrap-storybook.ts b/src/bin/bootstrap-storybook.ts new file mode 100644 index 0000000..5b2b198 --- /dev/null +++ b/src/bin/bootstrap-storybook.ts @@ -0,0 +1,38 @@ +import { BuildService } from '@types' + +import consola from 'consola' +import rimraf from 'rimraf' +import { resolve } from 'path' +import { writeFileSync } from 'fs' +import { getUserConfigSync } from 'src/utils' + +export function bootstrapStorybook(argv: BuildService.parsedArgs) { + consola.info('clear cache') + const rootDir = resolve(argv._[0] || '.') + const storybookRootDir = resolve(argv._[0] || '.storybook') + const storybookConfigPath = resolve(storybookRootDir, './storybook.build.config') + + rimraf.sync(storybookConfigPath) + + const config = getUserConfigSync('development', argv) + + delete config.webpack + + config.mode = process.env.NODE_ENV + + const template = `#!/usr/bin/env node +const path = require('path') +const rootDir = '${rootDir}' +const resolve = function (p) { + return path.resolve(rootDir, p) +} + +const config = ${JSON.stringify(config, null, 2)} + +config.resolve = resolve + +module.exports = config + ` + + writeFileSync(storybookConfigPath, template, 'utf8') +} diff --git a/src/config/index.ts b/src/config/index.ts index 6112e11..f1bd122 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,4 +1,4 @@ -import { getClientConfig } from './webpack.client.config' +import { getClientConfig, getClientConfigSync } from './webpack.client.config' import { getServerConfig } from './webpack.server.config' -export { getClientConfig, getServerConfig } +export { getClientConfig, getServerConfig, getClientConfigSync } diff --git a/src/config/webpack.client.config.ts b/src/config/webpack.client.config.ts index 8715903..b6ea6e6 100644 --- a/src/config/webpack.client.config.ts +++ b/src/config/webpack.client.config.ts @@ -6,14 +6,12 @@ import webpack from 'webpack' import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin' import { ConfigOptions } from '@types' -import { getClientDllPlugin, getExternals } from 'src/utils/plugins.webpack' +import { getClientDllPlugin, getExternals, getClientDllPluginSync } from 'src/utils/plugins.webpack' -export async function getClientConfig(options: ConfigOptions.options) { - const client = options.webpack ? options.webpack.client || {} : {} +function baseGetClientConfig(options: ConfigOptions.options) { const { externals, alias } = getExternals(options, 'client') const mode = options.webpack.mode || 'production' const isProd = mode === 'production' - return (merge as any)( getBaseConfig(options), { @@ -52,7 +50,27 @@ export async function getClientConfig(options: ConfigOptions.options) { ] }, getStyle(options, { isServer: false }), + ) +} + +export async function getClientConfig(options: ConfigOptions.options) { + const client = options.webpack ? options.webpack.client || {} : {} + + return (merge as any)( + baseGetClientConfig(options), await getClientDllPlugin(options), client, ) } + + +export function getClientConfigSync(options: ConfigOptions.options) { + const client = options.webpack ? options.webpack.client || {} : {} + + return (merge as any)( + baseGetClientConfig(options), + getClientDllPluginSync(options), + client, + ) + +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..d890efe --- /dev/null +++ b/src/index.ts @@ -0,0 +1,39 @@ +import { ConfigOptions } from '@types' +import { setWebpackSync } from 'src/utils' +import consola from 'consola' + +/** + * 使用现有的注入数据来解析用户的配置文件 + * @param INJECT_CONTEXT 使用 yarn build-storybook 后, 生成的 .storybook/storybook.build.config + * @param CONFIG 使用 yarn service:dev 后, 生成的 dist/xx/xx_config.js + */ +export function resoveBuildConfig(INJECT_CONTEXT, CONFIG) { + if (!INJECT_CONTEXT || !INJECT_CONTEXT.rootDir) { + consola.fatal('[resoveBuildConfig] INJECT_CONTEXT is', INJECT_CONTEXT) + process.exit(1) + } + if (!CONFIG) { + consola.fatal('[resoveBuildConfig] CONFIG is', CONFIG) + process.exit(1) + } + CONFIG = CONFIG.default(INJECT_CONTEXT) + // console.log('--------------------------') + // console.dir(INJECT_CONTEXT, { + // depth: null + // }) + // console.log('--------------------------') + // console.dir(CONFIG, { + // depth: null + // }) + // console.log('--------------------------') + const options: ConfigOptions.options = CONFIG + options.rootDir = INJECT_CONTEXT.rootDir + options.injectContext = INJECT_CONTEXT.injectContext + setWebpackSync(options, INJECT_CONTEXT.mode) + // console.log('--------------------------') + // console.dir(options, { + // depth: null + // }) + // console.log('--------------------------') + return options +} \ No newline at end of file diff --git a/src/utils/compiler.webpack.ts b/src/utils/compiler.webpack.ts index ff6caec..8a52cfe 100644 --- a/src/utils/compiler.webpack.ts +++ b/src/utils/compiler.webpack.ts @@ -151,6 +151,33 @@ export function compiler( } } +export function compilerConfigSync( + configOptions: BuildService.parsedArgs.config, + mode: ConfigOptions.webpackMode +) { + const entryName = `${mode === 'none' ? 'production' : mode}_config` + const { entry, output } = configOptions + if (!(entry && output)) { + consola.fatal('[compilerConfigSync]: entry or output is undefined') + return process.exit(1) + } + const path = resolve(output, `${entryName}.js`) + let config: any = {} + if (existsSync(path)) { + try { + const souce = readFileSync(path, 'utf-8') + config = requireFromString(souce) + } catch (error) { + consola.fatal('[compilerConfigSync]', error) + return process.exit(1) + } + return config + } else { + consola.fatal('[compilerConfigSync]: path is unExist!', path) + return process.exit(1) + } +} + /** * webpack 编译 配置文件 * @param configOptions 配置文件 设置 diff --git a/src/utils/index.ts b/src/utils/index.ts index dbecaf5..93c8250 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,7 +1,7 @@ import { resolve } from 'path' import { ConfigOptions, BuildService } from '@types' -import { getClientConfig, getServerConfig } from 'src/config' +import { getClientConfig, getServerConfig, getClientConfigSync } from 'src/config' import { existsSync, readFileSync } from 'fs' import consola from 'consola' import { createResolve } from 'src/utils/path' @@ -13,7 +13,7 @@ import compression from 'compression' import proxyMiddleware from 'http-proxy-middleware' import LRU from 'lru-cache' import HappyPack from 'happypack' -import { compilerConfig, compilerDll } from 'src/utils/compiler.webpack' +import { compilerConfig, compilerDll, compilerConfigSync } from 'src/utils/compiler.webpack' /** * 获取 根目录 地址 @@ -133,18 +133,12 @@ function setVersion(options: ConfigOptions.options) { } } -/** - * 设置 webpack - * @param options build 通用 webpack 配置 - * @param mode webpack 环境 - */ -async function setWebpack( +function baseSetWebpack( options: ConfigOptions.options, mode: ConfigOptions.webpackMode ) { options.webpack = options.webpack || {} options.webpack.mode = mode - options.webpack.client = await getClientConfig(options) options.webpack.server = getServerConfig(options) // const isProduction = mode ? mode !== 'development' : true // if (!isProduction) { @@ -159,6 +153,34 @@ async function setWebpack( return options } +/** + * 设置 webpack + * @param options build 通用 webpack 配置 + * @param mode webpack 环境 + */ +async function setWebpack( + options: ConfigOptions.options, + mode: ConfigOptions.webpackMode +) { + options.webpack.client = await getClientConfig(options) + baseSetWebpack(options, mode) + return options +} + +/** + * 同步 设置 webpack + * @param options build 通用 webpack 配置 + * @param mode webpack 环境 + */ +export function setWebpackSync( + options: ConfigOptions.options, + mode: ConfigOptions.webpackMode +) { + options.webpack.client = getClientConfigSync(options) + baseSetWebpack(options, mode) + return options +} + /** * 通过 用户的设定 完善 argv参数 * @param options build 通用 webpack 配置 @@ -207,6 +229,50 @@ function getInjectContext(configOptions: BuildService.parsedArgs.config) { return injectContext } +export function getUserConfigSync( + mode: ConfigOptions.webpackMode, + argv: BuildService.parsedArgs +) { + const configOptions = getConfigFileOptions(argv) + const injectContext = getInjectContext(configOptions) + const rootDir = getRootDir(argv) + + let options: any + + if (existsSync(configOptions.entry || '')) { + options = compilerConfigSync(configOptions, mode) + if (!options) { + options = {} + } else { + const args: ConfigOptions.getOptionsInject = { + argv, + mode, + resolve: createResolve(rootDir), + injectContext + } + options = options.default(args) + options.rootDir = options.rootDir || rootDir + } + if (options.default) { + options = options.default + } + } else if (argv['config-file'] !== 'buildService.config.js') { + consola.fatal('Could not load config file: ' + argv['config-file']) + return process.exit(1) + } + + if (!options.injectContext) { + options.injectContext = injectContext + } else { + options.injectContext = { + ...injectContext, + ...options.injectContext + } + } + + return options +} + /** * 获取 用户配置 * @param configOptions 配置文件 设置 @@ -529,9 +595,7 @@ export class RouterStackManagement { this.router = app._router this.startIndex = this.stack.length - 1 consola.info( - `RouterStackManagement 已经初始化完成 当前 startIndex = ${ - this.startIndex - }` + `RouterStackManagement 已经初始化完成 当前 startIndex = ${this.startIndex}` ) } @@ -573,9 +637,7 @@ export class RouterStackManagement { stack.splice(current.index, 1, stack.pop()) current.hotUpDateCount++ consola.info( - `stack Index = ${index} currentIndex = ${ - current.index - } 已经更新, 当前更新次数 = ${current.hotUpDateCount}` + `stack Index = ${index} currentIndex = ${current.index} 已经更新, 当前更新次数 = ${current.hotUpDateCount}` ) } } diff --git a/src/utils/plugins.webpack.ts b/src/utils/plugins.webpack.ts index 24de61c..980e7a6 100644 --- a/src/utils/plugins.webpack.ts +++ b/src/utils/plugins.webpack.ts @@ -41,11 +41,8 @@ export function getDllPlugin({ ] } } -/** - * 获取 客户端dll插件 - * @param options build 通用 webpack 配置 - */ -export async function getClientDllPlugin(options: ConfigOptions.options) { + +function baseGetClientDllPlugin(options: ConfigOptions.options) { if (!(options.webpack && options.webpack.dll && options.webpack.base)) return {} const dll = options.webpack.dll @@ -53,10 +50,6 @@ export async function getClientDllPlugin(options: ConfigOptions.options) { const nameArr = Object.keys(dll.entry) const plugins: any[] = [] - if (!existsSync(dll.path)) { - await compilerDll(options) - } - nameArr.forEach(name => { const path = join(dll.path, `/${name}.manifest.json`) if (existsSync(path)) { @@ -70,12 +63,38 @@ export async function getClientDllPlugin(options: ConfigOptions.options) { }) ) } else { - consola.fatal('path is not find!', path) + consola.fatal('[getClientDllPlugin] path is not find!', path) } }) return { plugins } + +} + +/** + * 获取 客户端dll插件 + * @param options build 通用 webpack 配置 + */ +export async function getClientDllPlugin(options: ConfigOptions.options) { + if (!(options.webpack && options.webpack.dll && options.webpack.base)) + return {} + const dll = options.webpack.dll + if (!existsSync(dll.path)) { + await compilerDll(options) + } + return baseGetClientDllPlugin(options) +} + +export function getClientDllPluginSync(options: ConfigOptions.options) { + if (!(options.webpack && options.webpack.dll && options.webpack.base)) + return {} + const dll = options.webpack.dll + if (!existsSync(dll.path)) { + consola.fatal('[getClientDllPluginSync] dll.path is not find!', dll.path) + process.exit(1) + } + return baseGetClientDllPlugin(options) } /** diff --git a/webpack.js b/webpack.js index 5d31bc8..bd502ed 100644 --- a/webpack.js +++ b/webpack.js @@ -12,7 +12,8 @@ module.exports = { build: resolve('./src/bin/build.ts'), start: resolve('./src/bin/start.ts'), dev: resolve('./src/bin/dev.ts'), - ['empty-module']: resolve('./src/utils/empty-module.js') + 'bootstrap-storybook': resolve('./src/bin/bootstrap-storybook.ts'), + 'empty-module': resolve('./src/utils/empty-module.js') }, output: { path: resolve('./dist'), diff --git a/yarn.lock b/yarn.lock index be4d741..acd5c6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1404,6 +1404,11 @@ babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" +babel-helper-vue-jsx-merge-props@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6" + integrity sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg== + babel-loader@^8.0.4: version "8.0.4" resolved "http://registry.npm.taobao.org/babel-loader/download/babel-loader-8.0.4.tgz#7bbf20cbe4560629e2e41534147692d3fecbdce6" @@ -1421,6 +1426,32 @@ babel-plugin-component@^1.1.1: dependencies: "@babel/helper-module-imports" "7.0.0-beta.35" +babel-plugin-jsx-event-modifiers@^2.0.2: + version "2.0.5" + resolved "https://registry.yarnpkg.com/babel-plugin-jsx-event-modifiers/-/babel-plugin-jsx-event-modifiers-2.0.5.tgz#93e6ebb5d7553bb08f9fedbf7a0bee3af09a0472" + integrity sha512-tWGnCk0whZ+nZcj9tYLw4+y08tPJXqaEjIxRJZS6DkUUae72Kz4BsoGpxt/Kow7mmgQJpvFCw8IPLSNh5rkZCg== + +babel-plugin-jsx-v-model@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jsx-v-model/-/babel-plugin-jsx-v-model-2.0.3.tgz#c396416b99cb1af782087315ae1d3e62e070f47d" + integrity sha512-SIx3Y3XxwGEz56Q1atwr5GaZsxJ2IRYmn5dl38LFkaTAvjnbNQxsZHO+ylJPsd+Hmv+ixJBYYFEekPBTHwiGfQ== + dependencies: + babel-plugin-syntax-jsx "^6.18.0" + html-tags "^2.0.0" + svg-tags "^1.0.0" + +babel-plugin-syntax-jsx@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= + +babel-plugin-transform-vue-jsx@^3.5.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.7.0.tgz#d40492e6692a36b594f7e9a1928f43e969740960" + integrity sha512-W39X07/n3oJMQd8tALBO+440NraGSF//Lo1ydd/9Nme3+QiRGFBb1Q39T9iixh0jZPPbfv3so18tNoIgLatymw== + dependencies: + esutils "^2.0.2" + babel-preset-latest-node@^2.0.0-beta.3: version "2.0.0-beta.3" resolved "http://registry.npm.taobao.org/babel-preset-latest-node/download/babel-preset-latest-node-2.0.0-beta.3.tgz#074811ef7c1529278c959d204be8fe8746622404" @@ -1438,6 +1469,17 @@ babel-preset-latest-node@^2.0.0-beta.3: "@babel/plugin-transform-function-name" "^7.0.0-beta.46" "@babel/plugin-transform-modules-commonjs" "^7.0.0-beta.46" +babel-preset-vue@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/babel-preset-vue/-/babel-preset-vue-2.0.2.tgz#cfadf1bd736125397481b5f8525ced0049a0c71f" + integrity sha1-z63xvXNhJTl0gbX4UlztAEmgxx8= + dependencies: + babel-helper-vue-jsx-merge-props "^2.0.2" + babel-plugin-jsx-event-modifiers "^2.0.2" + babel-plugin-jsx-v-model "^2.0.1" + babel-plugin-syntax-jsx "^6.18.0" + babel-plugin-transform-vue-jsx "^3.5.0" + balanced-match@^1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -3371,6 +3413,11 @@ html-minifier@^3.2.3: relateurl "0.2.x" uglify-js "3.4.x" +html-tags@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b" + integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos= + html-webpack-plugin@^3.2.0: version "3.2.0" resolved "http://registry.npm.taobao.org/html-webpack-plugin/download/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" @@ -6219,6 +6266,11 @@ supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" +svg-tags@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" + integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= + svgo@^1.0.0: version "1.1.1" resolved "http://registry.npm.taobao.org/svgo/download/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985"