Skip to content

Commit

Permalink
Merge ad264de into f4635c4
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Jul 31, 2020
2 parents f4635c4 + ad264de commit efb2519
Show file tree
Hide file tree
Showing 32 changed files with 418 additions and 266 deletions.
26 changes: 22 additions & 4 deletions README.md
Expand Up @@ -84,6 +84,8 @@ npm i san-ssr san-ssr-target-php

## 贡献指南

### 开发起步

欢迎任何类型的 Issue、完整的 Pull Request、或者不完整的 Pull Request。可以按照下面的步骤开始开发:

1. 克隆本仓库并 `npm install`
Expand All @@ -92,14 +94,30 @@ npm i san-ssr san-ssr-target-php
1. EXPECTED。test/cases/array-literal/expected.html 的内容。
2. SOURCE。compileToSource + render() 的结果,test/cases/array-literal/ssr.js 可查看生成的 ssr 代码。
3. RENDER。compileToRenderer + render() 的结果。
4. 如果 debug 符合预期,可以运行 `npm run integration` 来查看其它样例是否仍然正常。
4. 如果 debug 符合预期,可以运行 `npm run e2e` 来查看其它样例是否仍然正常。
5. 如果一切正常,运行 `npm run check` 来做最后的编码风格检查,和完整的测试。
6. 如果能够通过那么 Travis CI 就应该能通过,请发 PR 到本仓库。

Tips:
### debug 进阶

准备步骤:

1. 添加 `export PATH=$PATH:./bin` 到你的 Shell 配置里。
2. 让你的 zsh 进入项目目录后自动 `source ./bin/auto-complete`(如果你想自动补全 case 名字的话)。可以自定义 chpwd() 方法来实现。

debug 命令:

- `debug array-literal` 来执行这个 case 的所有编译方式:
1. 从 component.js 编译到 ssr.js 并执行 SSR 和 assert 结果
2. 从 component.ts 编译到 ssr.js 并执行 SSR 和 assert 结果
3. 从 component.js 编译到 render 函数并 SSR 和 assert 结果
- `render-by-source.js array-literal` 来跳过编译,直接执行 ssr.js 并 assert 结果。用于手改 ssr.js 调试。
- `render-onthefly.js array-literal` 来把这个 case 编译到 render 函数并 SSR 和 assert 结果。

注意:

1. 请添加 `PATH=$PATH:./bin` 到你的 Shell 配置,就可以直接用 `debug array-literal` 了。
2. 如果你在用 ZShell,先执行一次 ./bin/auto-complete 或把它添加到 .zshrc 里,`debug` 命令就可以自动补全样例名了。
- 如果没能把 ./bin 添加到 PATH,则执行命令需要全路径,例如:`./bin/debug array-literal`
- 如果没能执行 ./bin/auto-complete,则没法 Tab 自动补全 array-literal

[san]: https://github.com/baidu/san
[sanproject]: https://baidu.github.io/san-ssr/classes/_models_san_project_.sanproject.html
Expand Down
42 changes: 14 additions & 28 deletions bin/debug
Expand Up @@ -4,9 +4,9 @@ require('source-map-support/register')
const { assertSanHTMLEqual } = require('../dist/index')
const { execFileSync } = require('child_process')
const chalk = require('chalk')
const { readFileSync, readdirSync, writeFileSync } = require('fs')
const { readFileSync, readdirSync } = require('fs')
const { join } = require('path')
const { caseRoot, tsExists, compile, compileTS } = require('../dist/fixtures/case')
const { caseRoot, jsExists, tsExists, compileJS, compileTS } = require('../dist/fixtures/case')

const caseName = process.argv[2]
if (!caseName) {
Expand All @@ -22,52 +22,38 @@ const htmlPath = join(caseRoot, caseName, 'expected.html')
const expected = readFileSync(htmlPath, 'utf8')
console.log(chalk.cyan(`[EXPECT] ${caseName}`), expected)

if (!caseName.match(/-nsrc$/)) {
debugCompileToSource()
if (tsExists(caseName)) debugCompileToTSSource()
}
debugCompileToRenderer()
if (jsExists(caseName)) debugCompileJSToSource()
if (tsExists(caseName)) debugCompileTSToSource()
if (jsExists(caseName)) debugCompileToRenderer()

function debugCompileToSource () {
let got
function debugCompileJSToSource () {
compileJS(caseName)
const got = execFileSync(join(__dirname, `./render-by-source.js`), [caseName], { encoding: 'utf8' }).toString()
try {
const targetCode = compile(caseName)
const fileName = join(caseRoot, caseName, 'ssr.js')
writeFileSync(fileName, targetCode)

got = execFileSync(join(__dirname, `./render-by-source.js`), [caseName], { encoding: 'utf8' }).toString()
assertSanHTMLEqual(got, expected)
console.log(chalk.green(`[SOURCE] ${caseName}`), got)
console.log(chalk.green(`[SRC JS] ${caseName}`), got)
} catch (err) {
console.log(chalk.red(`[SOURCE] ${caseName}`), got)
console.error(err)
console.log(chalk.red(`[SRC JS] ${caseName}`), got)
}
}

function debugCompileToTSSource () {
let got
function debugCompileTSToSource () {
compileTS(caseName)
const got = execFileSync(join(__dirname, `./render-by-source.js`), [caseName], { encoding: 'utf8' }).toString()
try {
const targetCode = compileTS(caseName)
const fileName = join(caseRoot, caseName, 'ssr.js')
writeFileSync(fileName, targetCode)

got = execFileSync(join(__dirname, `./render-by-source.js`), [caseName], { encoding: 'utf8' }).toString()
assertSanHTMLEqual(got, expected)
console.log(chalk.green(`[SRC TS] ${caseName}`), got)
} catch (err) {
console.log(chalk.red(`[SRC TS] ${caseName}`), got)
console.error(err)
}
}

function debugCompileToRenderer () {
let got
const got = execFileSync(join(__dirname, `./render-onthefly.js`), [caseName], { encoding: 'utf8' }).toString()
try {
got = execFileSync(join(__dirname, `./render-onthefly.js`), [caseName], { encoding: 'utf8' }).toString()
assertSanHTMLEqual(got, expected)
console.log(chalk.green(`[RENDER] ${caseName}`), got)
} catch (err) {
console.log(chalk.red(`[RENDER] ${caseName}`), got)
console.error(err)
}
}
5 changes: 3 additions & 2 deletions jest.config.js
Expand Up @@ -4,13 +4,14 @@ module.exports = {
],
testMatch: [
'<rootDir>/test/unit/**/*.ts',
'<rootDir>/test/integration.spec.ts'
'<rootDir>/test/e2e.spec.ts'
],
transform: {
'^.+\\.ts$': 'babel-jest'
},
collectCoverageFrom: [
'src/**/*.ts'
'src/**/*.ts',
'!src/fixtures/**'
],
globals: {
tsConfig: {
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Expand Up @@ -25,12 +25,12 @@
"build": "tsc && chmod a+x dist/bin/*",
"watch": "tsc --watch",
"unit": "jest test/unit",
"integration": "jest test/integration.spec.ts",
"e2e": "jest test/e2e.spec.ts",
"perf": "node ./test/perf/index.js",
"check": "npm test && npm run lint",
"test": "npm run build && npm run unit && npm run integration",
"test": "npm run build && npm run unit && npm run e2e",
"coveralls": "npm run build && npm run coverage && cat coverage/lcov.info | coveralls",
"coverage": "jest test/unit test/integration.spec.ts --coverage",
"coverage": "jest test/unit test/e2e.spec.ts --coverage",
"version": "npm run build && npm run docs",
"semantic-release": "semantic-release"
},
Expand Down Expand Up @@ -75,7 +75,7 @@
"jest": "^24.9.0",
"mustache": "^3.2.0",
"san": "^3.9.1",
"san-html-cases": "^1.0.2",
"san-html-cases": "^2.0.0",
"san-ssr-target-fake-cmd": "^1.0.0",
"san-ssr-target-fake-esm": "^1.0.0",
"semantic-release": "^15.13.27",
Expand Down
34 changes: 22 additions & 12 deletions src/fixtures/case.ts
@@ -1,4 +1,4 @@
import { readFileSync, lstatSync, readdirSync, existsSync } from 'fs'
import { writeFileSync, readFileSync, lstatSync, readdirSync, existsSync } from 'fs'
import { join } from 'path'
import { SanProject } from '../models/san-project'
import ToJSCompiler from '../target-js'
Expand All @@ -10,6 +10,10 @@ export const caseRoot = join(__dirname, '../../node_modules/san-html-cases/src')
const tsConfigFilePath = join(__dirname, '../../test/tsconfig.json')
const sanProject = new SanProject(tsConfigFilePath)

export function jsExists (caseName: string) {
return existsSync(join(caseRoot, caseName, 'component.js'))
}

export function tsExists (caseName: string) {
return existsSync(join(caseRoot, caseName, 'component.ts'))
}
Expand All @@ -24,30 +28,36 @@ export function readExpected (caseName: string) {
return readFileSync(htmlPath, 'utf8')
}

export function compile (caseName: string, bareFunctionBody: boolean) {
export function compileJS (caseName: string, compileToFunctionBodyCode: true): string
export function compileJS (caseName: string, compileToFunctionBodyCode: boolean) {
debug('compile js', caseName)
const caseDir = join(caseRoot, caseName)
const jsFile = join(caseDir, 'component.js')
const ssrOnly = /-so/.test(caseName)
const targetCode = sanProject.compile(
jsFile,
ToJSCompiler,
{ ssrOnly, bareFunctionBody }
{ ssrOnly, bareFunctionBody: compileToFunctionBodyCode }
)
return targetCode
const targetFile = join(caseRoot, caseName, 'ssr.js')
return compileToFunctionBodyCode ? targetCode : writeFileSync(targetFile, targetCode)
}

export function compileTS (caseName: string, bareFunctionBody: boolean) {
export function compileTS (caseName: string) {
debug('compile ts', caseName)
const caseDir = join(caseRoot, caseName)
const tsFile = join(caseDir, 'component.ts')
const ssrOnly = /-so/.test(caseName)
const targetCode = sanProject.compile(
tsFile,
ToJSCompiler,
{ ssrOnly, bareFunctionBody }
)
return targetCode
for (const file of readdirSync(caseDir).filter(file => /\.ts$/.test(file))) {
const targetCode = sanProject.compile(
join(caseDir, file),
ToJSCompiler,
{ ssrOnly }
)
const targetFile = file === 'component.ts'
? join(caseDir, 'ssr.js')
: join(caseDir, file.replace(/\.ts$/, '.js'))
writeFileSync(targetFile, targetCode)
}
}

export function compileCaseToRenderer (caseName: string) {
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Expand Up @@ -5,7 +5,7 @@ import { ToJSCompileOptions } from './target-js/index'
import * as TypeGuards from './utils/type-guards'

// util functions
export { parseSanHTML, compareSanHTML, assertSanHTMLEqual } from './utils/case'
export { parseSanHTML, compareSanHTML, assertDeepEqual, assertSanHTMLEqual } from './utils/case'
export { autoCloseTags, booleanAttributes } from './utils/dom-util'
export { getANodePropByName } from './utils/anode-util'
export { Emitter } from './utils/emitter'
Expand All @@ -19,6 +19,7 @@ export { Compiler } from './models/compiler'
export { ComponentInfo, TypedComponentInfo, DynamicComponentInfo, isTypedComponentInfo } from './models/component-info'
export { ComponentReference } from './models/component-reference'
export { COMPONENT_RESERVED_MEMBERS } from './models/component'
export { CompileInput } from './models/options'

let defaultProject: SanProject

Expand Down
5 changes: 3 additions & 2 deletions src/models/component-info.ts
@@ -1,7 +1,8 @@
import { SanComponent, ANode, ComponentConstructor } from 'san'
import { SanComponent, ANode } from 'san'
import { ClassDeclaration } from 'ts-morph'
import { ComponentReference, DynamicComponentReference } from './component-reference'
import { getObjectLiteralPropertyKeys } from '../utils/ast-util'
import { ComponentClass } from '../models/component'

export type TagName = string

Expand Down Expand Up @@ -45,7 +46,7 @@ export class DynamicComponentInfo extends ComponentInfoImpl<DynamicComponentRefe
template: string,
root: ANode,
childComponents: Map<TagName | ANode, DynamicComponentReference>,
public readonly componentClass: ComponentConstructor<{}, {}>
public readonly componentClass: ComponentClass
) {
super(id, template, root, childComponents)
this.proto = Object.assign(componentClass.prototype, componentClass)
Expand Down
6 changes: 3 additions & 3 deletions src/models/component-reference.ts
Expand Up @@ -35,7 +35,7 @@ export interface DynamicComponentReference extends ComponentReference {
* 2. 对于 TypeScript San 源文件单独编译:可以从 import 语句映射到 ID。
* - 此时要求它是 sourceFile 内 top level 的名字,因此是唯一的
* - 例如:named export 组件,ID 为 ClassDeclaration#.getName()
* - 特例:default export 的组件,ID 为 0
* - 特例:default export 的组件,ID 为 "default"
*
* Note: ID 的作用只是确保文件中唯一,不可直接用于目标语言文件中的标识符
*/
Expand All @@ -57,8 +57,8 @@ export function getExportedComponentID (name: string) {
*
* // 对于如下 Component Reference,
* // 如果 id 为 AComponent 将无法定位到 a.san.ts 中的 class A
* { specifier: './a.san', id: '0', isDefault: true }
* { specifier: './a.san', id: 'default', isDefault: true }
*/
export function getDefaultExportedComponentID () {
return '0'
return 'default'
}
53 changes: 49 additions & 4 deletions src/runtime/index.ts
@@ -1,12 +1,57 @@
import { resolve } from 'path'
import { _ } from './underscore'
import { Resolver, createResolver } from './resolver'
import { SanData } from './san-data'
import { Emitter } from '../utils/emitter'
import { readStringSync } from '../utils/fs'

export const RUNTIME_FILES = [
/**
* 编译成源代码时,需要包含的运行时文件
*/
const RUNTIME_FILES = [
resolve(__dirname, '../../dist/runtime/underscore.js'),
resolve(__dirname, '../../dist/runtime/san-data.js')
resolve(__dirname, '../../dist/runtime/san-data.js'),
resolve(__dirname, '../../dist/runtime/resolver.js')
]

export function createRuntime () {
return { _, SanData }
export interface SanSSRRuntime {
/**
* 无状态的工具库,类似 lodash
*/
_: typeof _
/**
* SanData 的 SSR 运行时替代品
*/
SanData: typeof SanData
/**
* 组件 render、Class 解析器
*/
resolver: Resolver
/**
* 当前目标文件的 exports 对象
*/
exports: { [key: string]: any }
}

/**
* 产出运行时代码
*/
export function emitRuntime (emitter: Emitter) {
emitter.writeLine(`var sanSSRRuntime = { exports };`)
for (const file of RUNTIME_FILES) {
emitter.writeLine(`!(function (exports) {`)
emitter.indent()
emitter.writeLines(readStringSync(file))
emitter.unindent()
emitter.writeLine(`})(sanSSRRuntime);`)
}
emitter.writeLine(`sanSSRRuntime.resolver = sanSSRRuntime.createResolver(exports)`)
}

/**
* 编译成 render 函数时,使用的 helper
*/
export function createRuntime (): SanSSRRuntime {
const exports = {}
return { _, SanData, resolver: createResolver(exports), exports }
}

0 comments on commit efb2519

Please sign in to comment.