Skip to content

Commit

Permalink
feat: support multiple component files
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Oct 11, 2019
1 parent 6b180db commit 0376bba
Show file tree
Hide file tree
Showing 16 changed files with 190 additions and 70 deletions.
4 changes: 3 additions & 1 deletion bin/build-e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { ToJSCompiler } = require('../dist/compilers/to-js-compiler')
const tsconfigPath = resolve(__dirname, '../test/tsconfig.json')
const ccj = new ToJSCompiler(tsconfigPath)
const parser = ComponentParser.createUsingTsconfig(tsconfigPath)
const caseWithSplitedFiles = ['multi-component-files']

let html = ''
let specTpls = ''
Expand Down Expand Up @@ -54,7 +55,8 @@ function buildFile (caseDir) {
}

files.forEach(filename => {
// absolute path
if (caseWithSplitedFiles.includes(filename)) return
// absolute path
const abFilePath = join(caseDir, filename)
const stats = fs.statSync(abFilePath)
const isFile = stats.isFile()
Expand Down
26 changes: 12 additions & 14 deletions src/compilers/to-js-compiler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { readFileSync } from 'fs'
import { Component } from '../parsers/component'
import { JSEmitter } from '../emitters/js-emitter'
import { compileToSource } from './js-render-compiler'
import { ComponentParser } from '../parsers/component-parser'
import { transpileModule } from 'typescript'
import { Module } from '../loaders/cmd'
import { CMD } from '../loaders/cmd'
import { Project } from 'ts-morph'
import { SanSourceFile } from '../parsers/san-sourcefile'
import { getDefaultConfigPath } from '../parsers/tsconfig'
Expand Down Expand Up @@ -34,7 +34,7 @@ export class ToJSCompiler extends Compiler {
const emitter = new JSEmitter()
emitter.writeAnonymousFunction(['data', 'noDataOutput'], () => {
emitter.writeRuntime()
const componentClass = this.run(readFileSync(filepath, 'utf8'))
const componentClass = new CMD().require(filepath)
emitter.writeLines(compileToSource(componentClass))
})

Expand All @@ -47,25 +47,23 @@ export class ToJSCompiler extends Compiler {
emitter.writeRuntime()
const parser = new ComponentParser(this.project)
const component = parser.parseComponent(filepath)
const componentClass = this.compileAndRun(component.getComponentSourceFile())['default']
const componentClass = this.evalComponentClass(component)
emitter.writeLines(compileToSource(componentClass))
})
return emitter.fullText()
}

compileAndRun (source: SanSourceFile) {
const js = this.compileToJS(source)
return this.run(js)
}

run (js: string) {
Module.cache.delete(__filename)
return Module.require(__filename, js)
evalComponentClass (component: Component) {
const cmd = new CMD(filepath => {
const sourceFile = component.getModule(filepath)
if (!sourceFile) throw new Error(`file ${filepath} not found`)
const js = this.compileToJS(sourceFile)
return js
})
return cmd.require(component.getComponentFilepath()).default
}

compileToJS (source: SanSourceFile) {
// source = source.openInProject(this.project)
// this.transform(source)
const compilerOptions = this.tsConfigFilePath['compilerOptions']
const { diagnostics, outputText } =
transpileModule(source.getFullText(), { compilerOptions })
Expand Down
21 changes: 12 additions & 9 deletions src/compilers/to-php-compiler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { CMD } from '../loaders/cmd'
import { ComponentParser } from '../parsers/component-parser'
import { getInlineDependencyLiterals } from '../parsers/dependency-resolver'
import { isReserved } from '../utils/php-util'
import { generatePHPCode } from '../emitters/generate-php-code'
import { transformAstToPHP } from '../transformers/to-php'
import { ToJSCompiler } from './to-js-compiler'
import { readFileSync } from 'fs'
import { Project } from 'ts-morph'
import { compileRenderFunction } from './php-render-compiler'
import { Compiler } from './compiler'
Expand All @@ -21,19 +23,19 @@ export class ToPHPCompiler extends Compiler {
private root: string
private tsConfigFilePath: string
private nsPrefix: string
private removeExternals: string[]
private skipRequire: string[]
private toJSCompiler: ToJSCompiler
private project: Project

constructor ({
tsConfigFilePath = getDefaultConfigPath(),
root = tsConfigFilePath.split(sep).slice(0, -1).join(sep),
removeExternals = [],
skipRequire = [],
nsPrefix = ''
}) {
super({ fileHeader: '<?php\n' })
this.nsPrefix = nsPrefix
this.removeExternals = ['san-php-ssr', 'san', ...removeExternals]
this.skipRequire = ['san-php-ssr', 'san', ...skipRequire]
this.root = root
this.tsConfigFilePath = tsConfigFilePath
this.project = new Project({ tsConfigFilePath })
Expand All @@ -47,10 +49,9 @@ export class ToPHPCompiler extends Compiler {
} = {}) {
const emitter = new PHPEmitter(emitHeader)
const parser = new ComponentParser(this.project)

const component = parser.parseComponent(filepath)
const ComponentClass = this.toJSCompiler.evalComponentClass(component)

const ComponentClass = this.toJSCompiler.compileAndRun(component.getComponentSourceFile())['default']
compileRenderFunction({ ComponentClass, funcName, emitter, ns })
this.compileComponents(component, emitter)

Expand All @@ -65,7 +66,7 @@ export class ToPHPCompiler extends Compiler {
} = {}) {
const emitter = new PHPEmitter(emitHeader)

const ComponentClass = this.toJSCompiler.run(readFileSync(filepath, 'utf8'))
const ComponentClass = new CMD().require(filepath)
compileRenderFunction({ ComponentClass, funcName, emitter, ns })

emitter.writeRuntime()
Expand All @@ -75,9 +76,10 @@ export class ToPHPCompiler extends Compiler {
public compileToPHP (sourceFile: SanSourceFile) {
transformAstToPHP(sourceFile)
const tsconfig = require(this.tsConfigFilePath)

return generatePHPCode(
sourceFile,
this.removeExternals,
[...this.skipRequire, ...getInlineDependencyLiterals(sourceFile.origin)],
tsconfig['compilerOptions']
)
}
Expand All @@ -98,9 +100,10 @@ export class ToPHPCompiler extends Compiler {
}

private ns (file) {
const escapeName = x => isReserved(x) ? 'spsrNS' + camelCase(x) : x
let str = file
.slice(this.root.length, -extname(file).length)
.split(sep).map(camelCase).join('\\')
.split(sep).map(camelCase).map(escapeName).join('\\')
.replace(/^\\/, '')
if (this.nsPrefix) {
str = this.nsPrefix + str
Expand Down
10 changes: 7 additions & 3 deletions src/emitters/generate-php-code.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { compile } from 'ts2php'
import { SanSourceFile } from '../parser/san-sourcefile'
import { SanSourceFile } from '../parsers/san-sourcefile'
import debugFactory from 'debug'

export function generatePHPCode (sourceFile: SanSourceFile, removeExternals, compilerOptions) {
const debug = debugFactory('generate-php-code')

export function generatePHPCode (sourceFile: SanSourceFile, skipRequire, compilerOptions) {
debug('skipRequire:', skipRequire)
const modules = {}
for (const name of removeExternals) {
for (const name of skipRequire) {
modules[name] = { name, required: true }
}
const { errors, phpCode } = compile(sourceFile.getFilePath(), {
Expand Down
51 changes: 34 additions & 17 deletions src/loaders/cmd.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,48 @@
import { readFileSync } from 'fs'
import { dirname, resolve } from 'path'
import { isRelativePath } from '../parsers/dependency-resolver'

export class Module {
private filepath: string
private content: string
public filepath: string
public content: string
public exports = {}

constructor (filepath: string, content?: string) {
if (content === undefined) {
content = readFileSync(filepath, 'utf8')
}
constructor (filepath: string, content: string) {
this.filepath = filepath
this.content = content
}
}

type FileLoader = ((filepath: string) => string) | ({ [key:string]: string });

const defaultFileLoader = filepath => readFileSync(filepath, 'utf8')

static require (filepath: string, content?: string) {
if (!Module.cache.has(filepath)) {
const mod = new Module(filepath, content)
Module.cache.set(filepath, Module.load(mod))
export class CMD {
private readFile
public cache = new Map()

constructor (readFile: FileLoader = defaultFileLoader) {
if (typeof readFile === 'function') {
this.readFile = readFile
} else {
this.readFile = filepath => readFile[filepath] || ''
}
return Module.cache.get(filepath)
}

static load (mod: Module) {
const fn = new Function('module', 'exports', 'require', mod.content) // eslint-disable-line
fn(mod, mod.exports, require)
return mod.exports
require (filepath: string) {
if (!this.cache.has(filepath)) {
const mod = new Module(filepath, this.readFile(filepath))
// eslint-disable-next-line
const fn = new Function('module', 'exports', 'require', mod.content)
fn(mod, mod.exports, path => {
if (isRelativePath(path)) {
path = resolve(dirname(filepath), path)
return this.require(path)
}
return require(path)
})
return mod.exports
}
return this.cache.get(filepath)
}

static cache = new Map()
}
12 changes: 3 additions & 9 deletions src/parsers/component-parser.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { getComponentClassIdentifier, isChildClassOf } from '../transformers/ast-util'
import { getComponentClassIdentifier, isChildClassOf } from '../utils/ast-util'
import { normalizeComponentClass } from '../transformers/normalize-component'
import { SanSourceFile } from './san-sourcefile'
import { Project, SourceFile, ClassDeclaration } from 'ts-morph'
import { getDefaultConfigPath } from './tsconfig'
import { getDependenciesRecursively } from './dependency-resolver'
import { Component } from './component'
import debugFactory from 'debug'

Expand Down Expand Up @@ -38,14 +39,7 @@ export class ComponentParser {

private getComponentFiles (entryTSFile: string): Map<string, SourceFile> {
const sourceFile = this.project.getSourceFileOrThrow(entryTSFile)
// for (const importLiteral of sourceFile.getImportStringLiterals()) {
// console.log('s', importLiteral.getSourceFile().getFilePath())
// console.log('b', importLiteral.getText())
// }
return new Map([[
entryTSFile,
sourceFile
]])
return getDependenciesRecursively(sourceFile)
}

private parseSanSourceFile (sourceFile: SourceFile) {
Expand Down
32 changes: 28 additions & 4 deletions src/parsers/component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SanSourceFile } from './san-sourcefile'
import { extname } from 'path'
import { Component as SanComponent } from 'san'

// js ssr 还有一些缺陷,不可用来编译 ES6 或 TypeScript 编写的组件。
Expand All @@ -8,24 +9,47 @@ export class Component {
private files: Map<string, SanSourceFile> = new Map()
private entry: string
private componentClass: typeof SanComponent
private modules: Map<string, SanSourceFile> = new Map()

constructor (entry?: string) {
this.entry = entry
}

addFile (path: string, file: SanSourceFile) {
this.files.set(path, file)
getComponentFilepath () {
return this.entry
}

getComponentSourceFile (): SanSourceFile {
return this.files.get(this.entry)
}

addFile (filepath: string, sourceFile: SanSourceFile) {
const ext = extname(filepath)
const moduleSpecifier = filepath.substr(0, filepath.length - ext.length)
this.modules.set(moduleSpecifier, sourceFile)
this.files.set(filepath, sourceFile)
}

getFile (path: string) {
return this.files.get(path)
}

getFiles () {
return this.files
getModule (moduleSpecifier: string) {
if (this.files.has(moduleSpecifier)) {
return this.files.get(moduleSpecifier)
}
return this.modules.get(moduleSpecifier)
}

* getFilepaths () {
return this.files.keys()
}

* getSouceFiles () {
return this.files.values()
}

getFiles (): IterableIterator<[string, SanSourceFile]> {
return this.files.entries()
}
}
32 changes: 32 additions & 0 deletions src/parsers/dependency-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { SourceFile, ImportDeclaration } from 'ts-morph'

function * getInlineDeclarations (sourceFile: SourceFile) {
for (const decl of sourceFile.getImportDeclarations()) {
if (shouldInline(decl)) yield decl
}
}

export function * getInlineDependencies (sourceFile: SourceFile) {
for (const decl of getInlineDeclarations(sourceFile)) yield decl.getModuleSpecifierSourceFile()
}

export function * getInlineDependencyLiterals (sourceFile: SourceFile) {
for (const decl of getInlineDeclarations(sourceFile)) yield decl.getModuleSpecifierValue()
}

export function getDependenciesRecursively (sourceFile: SourceFile, result = new Map()) {
result.set(sourceFile.getFilePath(), sourceFile)
for (const dep of getInlineDependencies(sourceFile)) {
if (result.has(dep.getFilePath())) continue
getDependenciesRecursively(dep, result)
}
return result
}

export function shouldInline (decl: ImportDeclaration) {
return isRelativePath(decl.getModuleSpecifierValue()) && !decl.getModuleSpecifierSourceFile().isDeclarationFile()
}

export function isRelativePath (importLiteralValue: string) {
return /^\.\//.test(importLiteralValue)
}
8 changes: 4 additions & 4 deletions src/transformers/to-js.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { movePropertyInitiatorToPrototype } from '../transformers/ast-util'
import { movePropertyInitiatorToPrototype } from '../utils/ast-util'
import { SanSourceFile } from '../parsers/san-sourcefile'

export function transformAstToJS (sourceFile: SanSourceFile) {
for (const clazz of sourceFile.getComponentClassNames()) {
movePropertyInitiatorToPrototype(sourceFile.origin, sourceFile.getClass(clazz))
}
// for (const clazz of sourceFile.getComponentClassNames()) {
// movePropertyInitiatorToPrototype(sourceFile.origin, sourceFile.getClass(clazz))
// }
}
6 changes: 3 additions & 3 deletions src/transformers/to-php.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { removeObjectLiteralInitiator } from './ast-util'
import { removeObjectLiteralInitiator } from '../utils/ast-util'
import { SanSourceFile } from '../parsers/san-sourcefile'
import { isReserved } from '../utils/php-util'

const reservedNames = ['List']
const uselessComponentProps = ['components']

export function transformAstToPHP (sourceFile: SanSourceFile) {
Expand All @@ -20,7 +20,7 @@ export function transformAstToPHP (sourceFile: SanSourceFile) {

for (const clazz of sourceFile.getClasses()) {
const name = clazz.getName()
if (reservedNames.includes(name)) {
if (isReserved(name)) {
if (clazz.isExported()) {
throw new Error(`${name} is a reserved keyword in PHP`)
}
Expand Down
File renamed without changes.
Loading

0 comments on commit 0376bba

Please sign in to comment.