diff --git a/@types/index.d.ts b/@types/index.d.ts index fae997d..279c787 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -76,6 +76,11 @@ declare namespace build { * * 默认值 ./dist/build */ output?: string + + /** + * 注入的上下文 配置文件目录 + */ + injectContext?: any } /** @@ -152,18 +157,31 @@ declare namespace build { */ interface Request extends expressRequest { /** - * 当前 rander 设备上下文 + * 当前 注入的上下文 */ - renderContext?: any + injectContext?: any /** - * 当前 rander Env环境 key + * 当前 render Env环境 key */ renderEnv?: string[] } namespace getRender { - interface opts {} + /** + * 获取 render 配置参数 + */ + interface opts { + /** + * 注入的上下文 + */ + context: any + + /** + * 发布文件地址 默认为本地 + */ + publicPath?: string + } type renderFn = ( req: BuildService.Request, res: expressResponse, @@ -233,6 +251,14 @@ declare namespace build { interface getOptionsInject { argv: BuildService.parsedArgs mode: ConfigOptions.webpackMode + + /** + * 注入的上下文 + * * 用户自行配置 + * * 详情 参见 --injectContext + */ + injectContext: any + /** * 获取 正确的路径 * @param path 相对root路径 @@ -255,9 +281,9 @@ declare namespace build { rootDir?: string /** - * 当前 站点信息 + * 注入的上下文 */ - siteInfo?: any + injectContext?: any /** * render 配置 diff --git a/README.md b/README.md index 332f0e5..8bfaf62 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ### api 文档 TODO --  文档还没出 +- 文档还没出 - 但是有类型 在@types 中 ### 功能 @@ -13,6 +13,7 @@ - 开发项目 `service:dev` - 打包项目 `service:build` - 启动项目 `service:start` +- 检查构建 `service:ci` ### 命令参数 帮助 -h @@ -38,12 +39,12 @@ ### 特性 -#### 支持 SSR 使用webpack4 打包 css无法提取的问题 +#### 支持 SSR 使用 webpack4 打包 css 无法提取的问题 #### 支持 注入 Env -- 为了方便打包一遍, 多种环境公用一套代码. 比如: stage环境 release环境 线上环境 -- 代码仅仅与node环境一致, 而不是webpack打包时决定 +- 为了方便打包一遍, 多种环境公用一套代码. 比如: stage 环境 release 环境 线上环境 +- 代码仅仅与 node 环境一致, 而不是 webpack 打包时决定 #### 支持 webpack dll @@ -52,12 +53,12 @@ #### 支持 服务器端 express 开发 -- 提供 中间件 扩展 -- 提供 路由 扩展 +- 提供 中间件 扩展 +- 提供 路由 扩展 - 支持热部署开发 - 支持静态配置 - 支持转发配置 -##### 提供 中间件  扩展 +##### 提供 中间件 扩展 -##### 提供 路由  扩展 +##### 提供 路由 扩展 diff --git a/bin/utils.js b/bin/utils.js index ec4f283..4d56e51 100644 --- a/bin/utils.js +++ b/bin/utils.js @@ -13,11 +13,12 @@ function getArgv() { d: 'dll', c: 'config-file', fd: 'fileDescriptor', + ic: 'injectContext', cl: 'clear', v: 'version' }, boolean: ['h', 'd', 'v', 'cl'], - string: ['H', 'c', 'fd', 'e', 'o'], + string: ['H', 'c', 'fd', 'e', 'o', 'ic'], default: { c: 'build.config.json' } @@ -28,13 +29,14 @@ function getArgv() { Description Starts the application in ${process.env.NODE_ENV} mode Options - --port, -p A port number on which to start the application - --hostname, -H Hostname on which to start the application - --clear, --cl clear cache - --dll, -d build webpack dll - --version, -v look over version - --config-file, -c Path to config file (default: build.config.json) - --help, -h Displays this message + --port, -p A port number on which to start the application + --hostname, -H Hostname on which to start the application + --clear, --cl clear cache + --dll, -d build webpack dll + --injectContext, -ic Path to injectContext file + --version, -v look over version + --config-file, -c Path to config file (default: build.config.json) + --help, -h Displays this message `) process.exit(0) } diff --git a/package.json b/package.json index 82900c0..9580788 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bestminr/build", - "version": "1.5.9", + "version": "1.6.0", "license": "MIT", "scripts": { "publish": "npm publish --access=public", diff --git a/src/bin/dev.ts b/src/bin/dev.ts index 7c81e58..76ee64d 100644 --- a/src/bin/dev.ts +++ b/src/bin/dev.ts @@ -18,10 +18,9 @@ async function devMain(argv: BuildService.parsedArgs) { // const webpack: any = options.webpack // const clientConfig = webpack.client - // const serverConfig = webpack.server + // const serverConfig = webpack.client // console.log('-------------------------------------') - // console.log('clientConfig', clientConfig.module.rules[5].test instanceof RegExp) - // console.log('clientConfig', clientConfig.module.rules[5].test.constructor) + // console.log('clientConfig', JSON.stringify(clientConfig, null, 2)) // console.log('-------------------------------------') // console.log('getServerConfig', JSON.stringify(serverConfig, null, 2)) // console.log('-------------------------------------') diff --git a/src/config/webpack.client.config.ts b/src/config/webpack.client.config.ts index 9a7926f..755b06a 100644 --- a/src/config/webpack.client.config.ts +++ b/src/config/webpack.client.config.ts @@ -44,6 +44,6 @@ export async function getClientConfig(options: ConfigOptions.options) { }, getStyle(options, { isServer: false }), await getClientDllPlugin(options), - client + client, ) } diff --git a/src/config/webpack.dll.config.ts b/src/config/webpack.dll.config.ts index 524abf1..3e31604 100644 --- a/src/config/webpack.dll.config.ts +++ b/src/config/webpack.dll.config.ts @@ -7,6 +7,7 @@ import consola from 'consola' import webpack from 'webpack' import { getCommonConfig } from './webpack.common.config' +import VueSSRClientPlugin from 'vue-server-renderer/client-plugin' export function getDllConfig( options: ConfigOptions.options @@ -46,7 +47,12 @@ export function getDllConfig( maxEntrypointSize: 1024 * 500, maxAssetSize: 1024 * 500, hints: isProd ? 'warning' : false - } + }, + plugins: [ + new VueSSRClientPlugin({ + filename: 'vue-ssr-dll-manifest.json' + }), + ] }, getDllPlugin(dll), dll.webpack diff --git a/src/render.ts b/src/render.ts index 136e8e2..04d7f77 100644 --- a/src/render.ts +++ b/src/render.ts @@ -65,7 +65,7 @@ export function serverDevRender(app: Express) { clientManifest }), { - siteInfo: config.siteInfo + context: config.injectContext } ) ready(render) @@ -139,13 +139,31 @@ export function serverRender(app: Express) { if ( config.render && config.render.bundle && - config.render.options.clientManifestPath + config.render.options.clientManifestPath && + config.webpack && + config.webpack.client && + config.webpack.client.output ) { - const bundle = JSON.parse(readFileSync(config.render.bundle, 'utf-8')) const template = readFileSync(config.render.options.templatePath, 'utf-8') const clientManifest = JSON.parse( readFileSync(config.render.options.clientManifestPath, 'utf-8') ) + if (config.webpack.dll) { + const dllManifest = JSON.parse( + readFileSync(path.resolve(config.webpack.dll.path, './vue-ssr-dll-manifest.json'), 'utf-8') + ) + dllManifest.all.forEach((js: string) => { + clientManifest.all.push( 'dll/' + js) + }) + dllManifest.initial.forEach((js: string) => { + clientManifest.initial.unshift( 'dll/' + js) + }) + } + + const publicPath = config.webpack.client.output.publicPath + + clientManifest.publicPath = publicPath + const options = Object.assign( { ...BASE_RENDER_OPTIONS, @@ -155,10 +173,11 @@ export function serverRender(app: Express) { config.render.options ) - const renderer = createBundleRenderer(bundle, options) + const renderer = createBundleRenderer(config.render.bundle, options) const render: any = getRender(renderer, { - siteInfo: config.siteInfo + context: config.injectContext, + publicPath, }) app.use(cookieParser()) diff --git a/src/utils/index.ts b/src/utils/index.ts index ca06cc7..cbed5d2 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -48,16 +48,18 @@ function getConfigFileOptions( return process.exit(0) } - const { entry, output } = options + const { entry, output, injectContext } = options return { entry: resolve(rootDir, argv.entry || entry), - output: resolve(rootDir, argv.output || output) + output: resolve(rootDir, argv.output || output), + injectContext: resolve(rootDir, argv.injectContext || injectContext) } } else { return { entry: resolve(rootDir, argv.entry || './build.config.ts'), - output: resolve(rootDir, argv.output || './dist/build') + output: resolve(rootDir, argv.output || './dist/build'), + injectContext: resolve(rootDir, argv.injectContext || '') } } } @@ -133,24 +135,41 @@ function setStaticFileExts(options: ConfigOptions.options) { } /** - * 初始化并获取 BuildService 配置 - * @param argv BuildService 通用 启动参数 + * 设置 注入的上下文 + * @param configOptions 配置文件 设置 */ -export async function initConfig( - argv: BuildService.parsedArgs, - mode: ConfigOptions.webpackMode, - opt?: ConfigOptions.options.initConfigOptions -): Promise { - const rootDir = getRootDir(argv) - const configOptions = getConfigFileOptions(argv) - - let options: any = {} +function getInjectContext(configOptions: BuildService.parsedArgs.config) { + const injectContext: any = {} - if (opt && opt.clear) { - if (configOptions && configOptions.output) { - rimraf.sync(configOptions.output) + if (existsSync(configOptions.injectContext || '')) { + try { + const jsonString = readFileSync(configOptions.injectContext, { + encoding: 'utf-8' + }) + Object.assign(injectContext, JSON.parse(jsonString)) + } catch (error) { + consola.fatal(error) + return process.exit(0) } } + return injectContext +} + +/** + * 获取 用户配置 + * @param configOptions 配置文件 设置 + * @param injectContext 注入的上下文 + * @param argv BuildService 通用 启动参数 + * @param mode webpack 启动模式 + */ +async function getUserConfig( + configOptions: BuildService.parsedArgs.config, + injectContext: any, + mode: ConfigOptions.webpackMode, + argv: BuildService.parsedArgs +) { + const rootDir = getRootDir(argv) + let options: any if (existsSync(configOptions.entry || '')) { options = await compilerConfig(configOptions, mode, { rootDir }) @@ -160,7 +179,8 @@ export async function initConfig( const args: ConfigOptions.getOptionsInject = { argv, mode, - resolve: createResolve(rootDir) + resolve: createResolve(rootDir), + injectContext, } options = options.default(args) } @@ -169,19 +189,55 @@ export async function initConfig( } } else if (argv['config-file'] !== 'buildService.config.js') { consola.fatal('Could not load config file: ' + argv['config-file']) + return process.exit(0) } - if (typeof options.rootDir !== 'string') { - options.rootDir = rootDir + if (!options.injectContext) { + options.injectContext = injectContext + } else { + options.injectContext = { + ...injectContext, + ...options.injectContext + } } - options = setStaticFileExts(options) + return options +} - options = setBabelrc(options) +/** + * 初始化并获取 BuildService 配置 + * @param argv BuildService 通用 启动参数 + * @param mode webpack 启动模式 + */ +export async function initConfig( + argv: BuildService.parsedArgs, + mode: ConfigOptions.webpackMode, + opt?: ConfigOptions.options.initConfigOptions +): Promise { + const configOptions = getConfigFileOptions(argv) + + if (opt && opt.clear) { + if (configOptions && configOptions.output) { + rimraf.sync(configOptions.output) + } + } + + const injectContext = getInjectContext(configOptions) + + const options: ConfigOptions.options = await getUserConfig( + configOptions, + injectContext, + mode, + argv + ) + + setStaticFileExts(options) + + setBabelrc(options) await checkDll(argv, options) - options = await setWebpack(options, mode) + await setWebpack(options, mode) options.version = argv.version @@ -227,11 +283,7 @@ export function getConfig() { * @return express实例: app */ export function serverInit() { - const { - env, - statics, - proxyTable, - } = getConfig() + const { env, statics, proxyTable, injectContext } = getConfig() const app = express() app.use(compression({ threshold: 0 })) @@ -242,6 +294,8 @@ export function serverInit() { serverRenderDefaultEnv(app, env) + setInjectContext(injectContext) + return app } @@ -321,6 +375,14 @@ function serverRenderDefaultEnv(app: Express, env: any = []) { }) } +/** + * 设置 服务器端 注入的上下文 __INJECT_CONTEXT__ + * @param injectContext 注入的上下文 + */ +function setInjectContext(injectContext: any = {}) { + (process as any).__INJECT_CONTEXT__ = injectContext +} + /** * 基础 渲染 配置 */ @@ -333,7 +395,8 @@ export const BASE_RENDER_OPTIONS = { // this is only needed when vue-server-renderer is npm-linked // basedir: resolve(config.assetRoot), // recommended for performance - runInNewContext: 'once' + runInNewContext: 'once', + inject: true, } const isProduction = process.env.NODE_ENV === 'production' diff --git a/src/utils/plugins.webpack.ts b/src/utils/plugins.webpack.ts index 13f784c..e3ceae4 100644 --- a/src/utils/plugins.webpack.ts +++ b/src/utils/plugins.webpack.ts @@ -25,7 +25,7 @@ export function getDllPlugin({ new HtmlWebpackPlugin({ filename: join(templateOutput, 'index.template.html'), template, - inject: true, + inject: false, minify: { removeComments: false, collapseWhitespace: true, diff --git a/src/utils/render.ts b/src/utils/render.ts index e1022d2..65638fd 100644 --- a/src/utils/render.ts +++ b/src/utils/render.ts @@ -32,7 +32,7 @@ function getWindowEnv(renderEnv: string[]) { return serialize(env, { isJSON: true }) } -function getContextHead(req: BuildService.Request) { +function getContextHead(req: BuildService.Request, injectContext: any) { if (!req.renderEnv) { consola.fatal('req.renderEnv is undefined') return '' @@ -47,14 +47,17 @@ function getContextHead(req: BuildService.Request) { env.NODE_ENV === 'production' ? ';(function(){var s;(s=document.currentScript||document.scripts[document.scripts.length-1]).parentNode.removeChild(s);}());' : '' - return `` + )};window.__INJECT_CONTEXT__ = ${serialize(injectContext, { + isJSON: true + })}${autoRemove}` } const serverInfo = `express/${require('express/package.json').version} ` + - `vue-server-renderer/${require('vue-server-renderer/package.json').version}` + `vue-server-renderer/${require('vue-server-renderer/package.json').version} ` + + `@bestminr/build/${require('../../package.json').version} ` export function getRender( renderer: BundleRenderer, @@ -103,17 +106,17 @@ export function getRender( } const context = { + ...opts.context, pageInfo: { title: ' ', // default title @see util/mixins/index keywords: '', description: '' }, - siteInfo: getConfig().siteInfo || {}, headers: req.headers, url: req.url, cookies: req.cookies, - renderContext: req.renderContext || {}, - head: getContextHead(req) + injectContext: req.injectContext || {}, + head: getContextHead(req, opts.context) } renderer.renderToString(context, (err: any, html: string) => {