From 97d39bbce0380e424508a2b6a0ee3302048d9859 Mon Sep 17 00:00:00 2001 From: konpeki622 <512054675@qq.com> Date: Fri, 9 Jul 2021 12:23:12 +0800 Subject: [PATCH] feat: optimize index.html transformation --- src/ast-parse/astParse.ts | 20 +++- .../parsers/findJsxInScriptParser.ts | 6 +- src/ast-parse/parsers/index.ts | 2 +- .../transformations/addJsxTransformation.ts | 8 +- src/ast-parse/transformations/index.ts | 13 ++- .../indexHtmlTransformation.ts | 49 -------- .../indexHtmlTransformationVueCli.ts | 109 ++++++++++++++++++ .../indexHtmlTransformationWebpack.ts | 83 +++++++++++++ .../removeHtmlLangInTemplateTransformation.ts | 8 +- src/cli/cli.ts | 2 +- src/constants/constants.ts | 2 +- src/generate/geneIndexHtml.ts | 56 ++++----- src/template/index.html | 2 +- src/utils/common.ts | 4 + 14 files changed, 259 insertions(+), 105 deletions(-) delete mode 100644 src/ast-parse/transformations/indexHtmlTransformation.ts create mode 100644 src/ast-parse/transformations/indexHtmlTransformationVueCli.ts create mode 100644 src/ast-parse/transformations/indexHtmlTransformationWebpack.ts diff --git a/src/ast-parse/astParse.ts b/src/ast-parse/astParse.ts index f48c8f1..6ec37cc 100644 --- a/src/ast-parse/astParse.ts +++ b/src/ast-parse/astParse.ts @@ -3,8 +3,9 @@ import { parsersMap, ParserType } from './parsers/index' import { SFCDescriptor, vueSfcAstParser } from '@originjs/vue-sfc-ast-parser' import * as globby from 'globby' import fs from 'fs' -import { JSCodeshift } from 'jscodeshift/src/core'; -import { ESLintProgram } from 'vue-eslint-parser/ast'; +import { JSCodeshift } from 'jscodeshift/src/core' +import { ESLintProgram } from 'vue-eslint-parser/ast' +import { Config } from '../config/config' export type FileInfo = { path: string, @@ -27,6 +28,10 @@ export type ParsingResultOccurrence = { type: ParserType } +export type TransformationParams = { + config: Config +} + export type TransformationResult = { fileInfo: FileInfo, content: string, @@ -46,11 +51,16 @@ export type AstParsingResult = { transformationResult: AstTransformationResult } -export function astParseRoot (rootDir: string): AstParsingResult { +export async function astParseRoot (rootDir: string, config: Config): Promise { const resolvedPaths : string[] = globby.sync(rootDir.replace(/\\/g, '/')) const parsingResults: ParsingResult = {} const transformationResults: AstTransformationResult = {} - resolvedPaths.forEach(filePath => { + + const transformationParams: TransformationParams = { + config: config + } + + resolvedPaths.forEach(async filePath => { // skip files in node_modules if (filePath.indexOf('/node_modules/') >= 0) { return @@ -77,7 +87,7 @@ export function astParseRoot (rootDir: string): AstParsingResult { } // execute the transformation - tempTransformationResult = transformation.astTransform(fileInfo) + tempTransformationResult = await transformation.astTransform(fileInfo, transformationParams) if (tempTransformationResult == null) { continue } diff --git a/src/ast-parse/parsers/findJsxInScriptParser.ts b/src/ast-parse/parsers/findJsxInScriptParser.ts index d59e0c8..6fb80dc 100644 --- a/src/ast-parse/parsers/findJsxInScriptParser.ts +++ b/src/ast-parse/parsers/findJsxInScriptParser.ts @@ -1,10 +1,10 @@ -import { ASTParse, ParserType } from './index'; -import { FileInfo, parseVueSfc, ParsingResultOccurrence, VueSFCContext } from '../astParse'; +import { ASTParse, ParserType } from './index' +import { FileInfo, parseVueSfc, ParsingResultOccurrence, VueSFCContext } from '../astParse' export const astParse: ASTParse = (fileInfo: FileInfo) => { const context: VueSFCContext = parseVueSfc(fileInfo) if (!context.scriptAST || context.scriptAST.findJSXElements().length === 0) { - return null; + return null } const results: ParsingResultOccurrence[] = [] diff --git a/src/ast-parse/parsers/index.ts b/src/ast-parse/parsers/index.ts index de8d252..1e2f1ae 100644 --- a/src/ast-parse/parsers/index.ts +++ b/src/ast-parse/parsers/index.ts @@ -1,4 +1,4 @@ -import { FileInfo, ParsingResultOccurrence } from '../astParse'; +import { FileInfo, ParsingResultOccurrence } from '../astParse' export type ASTParse = { (fileInfo: FileInfo, params: Params): ParsingResultOccurrence[] | null diff --git a/src/ast-parse/transformations/addJsxTransformation.ts b/src/ast-parse/transformations/addJsxTransformation.ts index b4bd488..5e65b1d 100644 --- a/src/ast-parse/transformations/addJsxTransformation.ts +++ b/src/ast-parse/transformations/addJsxTransformation.ts @@ -1,12 +1,12 @@ import type { ASTTransformation } from './index' import { TransformationType } from './index' import { stringifyDescriptor } from '@originjs/vue-sfc-ast-parser' -import { FileInfo, VueSFCContext, parseVueSfc, TransformationResult } from '../astParse'; +import { FileInfo, VueSFCContext, parseVueSfc, TransformationResult } from '../astParse' -export const astTransform:ASTTransformation = (fileInfo: FileInfo) => { +export const astTransform:ASTTransformation = async (fileInfo: FileInfo) => { const context: VueSFCContext = parseVueSfc(fileInfo) if (!context.scriptAST || context.scriptAST.findJSXElements().length === 0) { - return null; + return null } // if jsx element is found, the lang of script should be 'jsx' @@ -17,7 +17,7 @@ export const astTransform:ASTTransformation = (fileInfo: FileInfo) => { content: stringifyDescriptor(descriptor), type: TransformationType.addJsxTransformation } - return result; + return result } export const needReparse: boolean = false diff --git a/src/ast-parse/transformations/index.ts b/src/ast-parse/transformations/index.ts index 0adc419..7507879 100644 --- a/src/ast-parse/transformations/index.ts +++ b/src/ast-parse/transformations/index.ts @@ -1,7 +1,7 @@ -import { FileInfo, TransformationResult } from '../astParse'; +import { FileInfo, TransformationResult, TransformationParams } from '../astParse' -export type ASTTransformation = { - (fileInfo: FileInfo, params: Params): TransformationResult | null +export type ASTTransformation = { + (fileInfo: FileInfo, params: Params): Promise | null } export enum TransformationType { @@ -10,7 +10,9 @@ export enum TransformationType { // eslint-disable-next-line no-unused-vars removeHtmlLangInTemplateTransformation = 'removeHtmlLangInTemplateTransformation', // eslint-disable-next-line no-unused-vars - indexHtmlTransformation = 'indexHtmlTransformation' + indexHtmlTransformationVueCli = 'indexHtmlTransformationVueCli', + // eslint-disable-next-line no-unused-vars + indexHtmlTransformationWebpack = 'indexHtmlTransformationWebpack', } export const transformationMap: { @@ -24,5 +26,6 @@ export const transformationMap: { } = { addJsxTransformation: require('./addJsxTransformation'), removeHtmlLangInTemplateTransformation: require('./removeHtmlLangInTemplateTransformation'), - indexHtmlTransformation: require('./indexHtmlTransformation') + indexHtmlTransformationVueCli: require('./indexHtmlTransformationVueCli'), + indexHtmlTransformationWebpack: require('./indexHtmlTransformationWebpack') } diff --git a/src/ast-parse/transformations/indexHtmlTransformation.ts b/src/ast-parse/transformations/indexHtmlTransformation.ts deleted file mode 100644 index 698d076..0000000 --- a/src/ast-parse/transformations/indexHtmlTransformation.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { ASTTransformation } from './index' -import { TransformationType } from './index' -import { FileInfo, TransformationResult } from '../astParse' -import { ESLintProgram } from 'vue-eslint-parser/ast'; -import * as parser from 'vue-eslint-parser'; -import { Node } from 'vue-eslint-parser/ast/nodes' - -const INDEX_HTML_PATH: string = 'public/index.html' -const templateStart: string = '' - -export const astTransform:ASTTransformation = (fileInfo: FileInfo) => { - if (!fileInfo.path.replace('\\', '/').endsWith(INDEX_HTML_PATH)) { - return null - } - - // add template tags for vue-eslint-parser - const htmlContent = `${templateStart}${fileInfo.source}${templateEnd}` - const htmlAST : ESLintProgram = parser.parse(htmlContent, { sourceType: 'module' }) - const root: Node = htmlAST.templateBody - let bodyNode - parser.AST.traverseNodes(root, { - enterNode (node: Node) { - if (node.type === 'VElement' && node.name === 'body') { - bodyNode = node - } - }, - leaveNode () {} - }) - let transformedHtml: string = htmlContent.slice(0, bodyNode.endTag.range[0]) + '{0}' + htmlContent.slice(bodyNode.endTag.range[0]) - // remove template tags - transformedHtml = transformedHtml.slice(0, transformedHtml.length - templateEnd.length) - transformedHtml = transformedHtml.slice(templateStart.length) - const result: TransformationResult = { - fileInfo: fileInfo, - content: transformedHtml, - type: TransformationType.removeHtmlLangInTemplateTransformation - } - - return result -} - -export const needReparse: boolean = false - -export const needWriteToOriginFile: boolean = false - -export const extensions: string[] = ['.html'] - -export const transformationType: TransformationType = TransformationType.indexHtmlTransformation diff --git a/src/ast-parse/transformations/indexHtmlTransformationVueCli.ts b/src/ast-parse/transformations/indexHtmlTransformationVueCli.ts new file mode 100644 index 0000000..9ff107a --- /dev/null +++ b/src/ast-parse/transformations/indexHtmlTransformationVueCli.ts @@ -0,0 +1,109 @@ +import type { ASTTransformation } from './index' +import { TransformationType } from './index' +import { FileInfo, TransformationResult, TransformationParams } from '../astParse' +import { ESLintProgram } from 'vue-eslint-parser/ast' +import * as parser from 'vue-eslint-parser' +import { Node } from 'vue-eslint-parser/ast/nodes' +import { stringSplice } from '../../utils/common' +import { parseVueCliConfig } from '../../config/parse' +import path from 'path' +import fs from 'fs' + +const templateStart: string = '' + +export const astTransform:ASTTransformation = async (fileInfo: FileInfo, transformationParams?: TransformationParams) => { + if (!transformationParams) { + return null + } + + if (transformationParams.config.projectType === 'webpack') { + return null + } + + const rootDir: string = transformationParams.config.rootDir + let indexPath: string + if (fs.existsSync(path.resolve(rootDir, 'public/index.html'))) { + indexPath = path.resolve(rootDir, 'public/index.html').replace(/\\/g, '/') + } else if (fs.existsSync(path.resolve(rootDir, 'index.html'))) { + indexPath = path.resolve(rootDir, 'index.html').replace(/\\/g, '/') + } else { + indexPath = null + } + if (!indexPath || !fileInfo.path.endsWith(indexPath)) { + return null + } + + // add template tags for vue-eslint-parser + let htmlContent = `${templateStart}${fileInfo.source}${templateEnd}` + const htmlAST : ESLintProgram = parser.parse(htmlContent, { sourceType: 'module' }) + const root: Node = htmlAST.templateBody + + const afterIndentLength: number = 1 + let frontIndentLength: number = 0 + let offset: number = 0 + + const vueConfigFile = path.resolve(rootDir, 'vue.config.js') + const vueConfig = await parseVueCliConfig(vueConfigFile) + const jspRegExp = /<%(=|-)(.|\s|\r\n)*%>/g + const jspIdentifierRegExp = /(<%|=|-|\s|\r\n|%>)/g + const jspMap = {} + + let bodyNode + + parser.AST.traverseNodes(root, { + enterNode (node: Node) { + // replace jsp tags + if ((node.type === 'VLiteral' || node.type === 'VText') && jspRegExp.test(node.value)) { + node.value.match(jspRegExp).forEach(jspSection => { + const jspValue: string = jspSection.replace(jspIdentifierRegExp, '') + if (!jspMap[jspSection] && jspValue === 'BASE_URL') { + const publicPath: string = + process.env.PUBLIC_URL || vueConfig.publicPath || vueConfig.baseUrl || '' + jspMap[jspSection] = path.relative(rootDir, path.resolve(rootDir, publicPath)).replace(/\\/g, '/') + '/' + } else if (!jspMap[jspSection]) { + jspMap[jspSection] = process.env[jspValue] ? process.env[jspValue] : '' + } + }) + } + if (node.type === 'VElement' && node.name === 'body') { + bodyNode = node + } else if (node.type === 'VElement' && node.name === 'script') { + const nodeAttrs = node.startTag.attributes + // remove original entry scripts with spaces + if (nodeAttrs[0]?.key.name === 'type' && nodeAttrs[0].value.type === 'VLiteral' && nodeAttrs[0].value.value === 'module' && nodeAttrs[1].key.name === 'src') { + frontIndentLength = node.loc.start.column + htmlContent = stringSplice(htmlContent, node.range[0] - frontIndentLength, node.range[1] + afterIndentLength, offset) + offset += node.range[1] - node.range[0] + frontIndentLength + afterIndentLength + 1 + } + } + }, + leaveNode () {} + }) + + let transformedHtml: string = htmlContent.slice(0, bodyNode.endTag.range[0] - offset) + '{0}' + htmlContent.slice(bodyNode.endTag.range[0] - offset) + // remove template tags + transformedHtml = transformedHtml.slice(0, transformedHtml.length - templateEnd.length) + transformedHtml = transformedHtml.slice(templateStart.length) + + Object.keys(jspMap).forEach(key => { + const keyRegExp: RegExp = new RegExp(key, 'g') + transformedHtml = transformedHtml.replace(keyRegExp, jspMap[key]) + }) + + const result: TransformationResult = { + fileInfo: fileInfo, + content: transformedHtml, + type: TransformationType.removeHtmlLangInTemplateTransformation + } + + return result +} + +export const needReparse: boolean = false + +export const needWriteToOriginFile: boolean = false + +export const extensions: string[] = ['.html'] + +export const transformationType: TransformationType = TransformationType.indexHtmlTransformationVueCli diff --git a/src/ast-parse/transformations/indexHtmlTransformationWebpack.ts b/src/ast-parse/transformations/indexHtmlTransformationWebpack.ts new file mode 100644 index 0000000..e1ec5b6 --- /dev/null +++ b/src/ast-parse/transformations/indexHtmlTransformationWebpack.ts @@ -0,0 +1,83 @@ +import type { ASTTransformation } from './index' +import { TransformationType } from './index' +import { FileInfo, TransformationResult, TransformationParams } from '../astParse' +import { ESLintProgram } from 'vue-eslint-parser/ast' +import * as parser from 'vue-eslint-parser' +import { Node } from 'vue-eslint-parser/ast/nodes' +import { stringSplice } from '../../utils/common' +import path from 'path' +import fs from 'fs' + +const templateStart: string = '' + +export const astTransform:ASTTransformation = async (fileInfo: FileInfo, transformationParams?: TransformationParams) => { + if (!transformationParams) { + return null + } + + if (transformationParams.config.projectType !== 'webpack') { + return null + } + + const rootDir: string = transformationParams.config.rootDir + let indexPath: string + if (fs.existsSync(path.resolve(rootDir, 'index.html'))) { + indexPath = path.resolve(rootDir, 'index.html').replace(/\\/g, '/') + } else { + indexPath = null + } + + if (!indexPath || !fileInfo.path.endsWith(indexPath)) { + return null + } + + // add template tags for vue-eslint-parser + let htmlContent = `${templateStart}${fileInfo.source}${templateEnd}` + const htmlAST : ESLintProgram = parser.parse(htmlContent, { sourceType: 'module' }) + const root: Node = htmlAST.templateBody + + const afterIndentLength: number = 1 + let frontIndentLength: number = 0 + let offset: number = 0 + + let bodyNode + + parser.AST.traverseNodes(root, { + enterNode (node: Node) { + if (node.type === 'VElement' && node.name === 'body') { + bodyNode = node + } else if (node.type === 'VElement' && node.name === 'script') { + const nodeAttrs = node.startTag.attributes + // remove original entry scripts with spaces + if (nodeAttrs[0]?.key.name === 'type' && nodeAttrs[0].value.type === 'VLiteral' && nodeAttrs[0].value.value === 'module' && nodeAttrs[1].key.name === 'src') { + frontIndentLength = node.loc.start.column + htmlContent = stringSplice(htmlContent, node.range[0] - frontIndentLength, node.range[1] + afterIndentLength, offset) + offset += node.range[1] - node.range[0] + frontIndentLength + afterIndentLength + 1 + } + } + }, + leaveNode () {} + }) + + let transformedHtml: string = htmlContent.slice(0, bodyNode.endTag.range[0] - offset) + '{0}' + htmlContent.slice(bodyNode.endTag.range[0] - offset) + // remove template tags + transformedHtml = transformedHtml.slice(0, transformedHtml.length - templateEnd.length) + transformedHtml = transformedHtml.slice(templateStart.length) + + const result: TransformationResult = { + fileInfo: fileInfo, + content: transformedHtml, + type: TransformationType.removeHtmlLangInTemplateTransformation + } + + return result +} + +export const needReparse: boolean = false + +export const needWriteToOriginFile: boolean = false + +export const extensions: string[] = ['.html'] + +export const transformationType: TransformationType = TransformationType.indexHtmlTransformationWebpack diff --git a/src/ast-parse/transformations/removeHtmlLangInTemplateTransformation.ts b/src/ast-parse/transformations/removeHtmlLangInTemplateTransformation.ts index c13eb2a..dd584bf 100644 --- a/src/ast-parse/transformations/removeHtmlLangInTemplateTransformation.ts +++ b/src/ast-parse/transformations/removeHtmlLangInTemplateTransformation.ts @@ -1,12 +1,12 @@ import type { ASTTransformation } from './index' import { TransformationType } from './index' import { stringifyDescriptor } from '@originjs/vue-sfc-ast-parser' -import { FileInfo, parseVueSfc, TransformationResult, VueSFCContext } from '../astParse'; +import { FileInfo, parseVueSfc, TransformationResult, VueSFCContext } from '../astParse' -export const astTransform:ASTTransformation = (fileInfo: FileInfo) => { +export const astTransform:ASTTransformation = async (fileInfo: FileInfo) => { const context: VueSFCContext = parseVueSfc(fileInfo) if (!context.descriptor.template || !context.descriptor.template.attrs!.lang) { - return null; + return null } if (context.descriptor.template.attrs.lang === 'html') { @@ -18,7 +18,7 @@ export const astTransform:ASTTransformation = (fileInfo: FileInfo) => { content: stringifyDescriptor(context.descriptor), type: TransformationType.removeHtmlLangInTemplateTransformation } - return result; + return result } export const needReparse : boolean = false diff --git a/src/cli/cli.ts b/src/cli/cli.ts index c7c6630..4512da7 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -43,7 +43,7 @@ export async function start (config : Config): Promise { const cwd = process.cwd() const rootDir = path.resolve(config.rootDir) - const astParsingResult: AstParsingResult = astParseRoot(rootDir) + const astParsingResult: AstParsingResult = await astParseRoot(rootDir, config) genePackageJson(path.resolve(rootDir, 'package.json')) await geneViteConfig(rootDir, rootDir, config) diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 4c7373a..2398e9e 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -1,6 +1,6 @@ export const DEFAULT_VUE_VERSION = 2 export const VUE_COMPILER_SFC_VERSION = '^3.0.5' -export const VITE_VERSION = '^2.1.5' +export const VITE_VERSION = '2.3.8' export const VITE_PLUGIN_VUE_VERSION = '^1.2.1' export const VITE_PLUGIN_VUE_JSX_VERSION = '^1.1.5' export const VITE_PLUGIN_VUE_TWO_VERSION = '^1.5.1' diff --git a/src/generate/geneIndexHtml.ts b/src/generate/geneIndexHtml.ts index 499e145..518729c 100644 --- a/src/generate/geneIndexHtml.ts +++ b/src/generate/geneIndexHtml.ts @@ -3,42 +3,22 @@ import path from 'path' import { readSync, writeSync } from '../utils/file' import { isObject, stringFormat } from '../utils/common' import { Config } from '../config/config' -import { AstParsingResult } from '../ast-parse/astParse'; -import { TransformationType } from '../ast-parse/transformations'; +import { AstParsingResult } from '../ast-parse/astParse' +import { TransformationType } from '../ast-parse/transformations' -export function geneIndexHtml (rootDir: string, config: Config, astParsingResult: AstParsingResult): void { - const baseFilePath = path.resolve(rootDir, 'index.html') - const vueCliFilePath = path.resolve(rootDir, 'public/index.html') - let htmlContent +export function geneIndexHtml (rootDir: string, config: Config, astParsingResult?: AstParsingResult): void { + const outputIndexPath: string = path.resolve(rootDir, 'index.html') + const projectType: string = config.projectType let entries : string[] = [] - if (config.entry !== undefined && config.entry !== '' && config.entry.length !== 0 && config.entry !== {}) { + if (config.entry !== undefined && config.entry !== '' && config.entry.length !== 0 && JSON.stringify(config.entry) !== '{}') { entries = getEntries(config.entry) } else { entries = getDefaultEntries(rootDir) } - let injectedContent - if (config.projectType !== 'webpack' && fs.existsSync(vueCliFilePath)) { - injectedContent = generateWithVueCliPublicIndex(astParsingResult, entries) - } else if (fs.existsSync(baseFilePath)) { - htmlContent = readSync(baseFilePath).replace(/<%.*URL.*%>/g, '') - injectedContent = injectHtml(htmlContent, entries) - } else { - htmlContent = readSync(path.resolve(path.resolve('src/template/index.html'))) - injectedContent = injectHtml(htmlContent, entries) - } - writeSync(baseFilePath, injectedContent) -} - -export function injectHtml (source: string, entries: string[]): string { - const bodyRegex = /]*>((.|[\n\r])*)<\/body>/im - let body = '\n' - body += '
\n' - body += generateEntriesHtml(entries) - body += '' - const result = source.replace(bodyRegex, body) - return result + const injectedContent = generateWithVueCliPublicIndex(astParsingResult, entries, projectType) + writeSync(outputIndexPath, injectedContent) } function generateEntriesHtml (entries: string[]): string { @@ -52,9 +32,23 @@ function generateEntriesHtml (entries: string[]): string { return entriesHtml } -export function generateWithVueCliPublicIndex (astParsingResult: AstParsingResult, entries: string[]): string { - const indexHtmlContent: string = astParsingResult.transformationResult[TransformationType.indexHtmlTransformation][0].content - return stringFormat(indexHtmlContent, generateEntriesHtml(entries)) +export function generateWithVueCliPublicIndex (astParsingResult: AstParsingResult, entries: string[], projectType: string): string { + let indexHtmlTransformationResult + + if (!astParsingResult) { + indexHtmlTransformationResult = null + } else if (projectType === 'webpack') { + indexHtmlTransformationResult = astParsingResult.transformationResult[TransformationType.indexHtmlTransformationWebpack] + } else { + indexHtmlTransformationResult = astParsingResult.transformationResult[TransformationType.indexHtmlTransformationVueCli] + } + + if (indexHtmlTransformationResult) { + const indexHtmlContent: string = indexHtmlTransformationResult[0].content + return stringFormat(indexHtmlContent, generateEntriesHtml(entries)) + } else { + return readSync(path.resolve('src/template/index.html')) + } } function getDefaultEntries (rootDir: string): string[] { diff --git a/src/template/index.html b/src/template/index.html index 0a51fec..52d9797 100644 --- a/src/template/index.html +++ b/src/template/index.html @@ -10,4 +10,4 @@
- \ No newline at end of file + diff --git a/src/utils/common.ts b/src/utils/common.ts index 5c90843..7b76536 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -7,3 +7,7 @@ export function stringFormat (formatString: string, ...args: string[]) { return typeof args[number] !== 'undefined' ? args[number] : match }) } + +export function stringSplice (source: string, start: number, end: number, offset: number = 0) { + return source.substring(0, start - offset) + source.substring(end - offset) +}