Skip to content

Commit

Permalink
feat: support san@3.9.0
Browse files Browse the repository at this point in the history
BREAKING CHANGE

- san<@3.9 is no longer supported.
  • Loading branch information
harttle committed Jul 6, 2020
1 parent 437810a commit 5de18cc
Show file tree
Hide file tree
Showing 16 changed files with 77 additions and 72 deletions.
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.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"handlebars": "^4.5.3",
"jest": "^24.9.0",
"mustache": "^3.2.0",
"san": "^3.8.0",
"san": "^3.9.1",
"san-ssr-target-fake-cmd": "^1.0.0",
"san-ssr-target-fake-esm": "^1.0.0",
"semantic-release": "^15.13.27",
Expand All @@ -93,7 +93,7 @@
"yargs": "^14.2.0"
},
"peerDependencies": {
"san": "~3.8.0"
"san": "^3.9.1"
},
"release": {
"branch": "master",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { autoCloseTags, booleanAttributes } from './utils/dom-util'
export { getANodePropByName } from './utils/anode-util'
export { Emitter } from './utils/emitter'
export { TypeGuards }
export { _ } from './runtime/underscore'

// class types
export { SanSourceFile, TypedSanSourceFile, DynamicSanSourceFile, isTypedSanSourceFile } from './models/san-source-file'
Expand Down
15 changes: 14 additions & 1 deletion src/models/component-reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,20 @@ export function getComponentClassID (id: number) {
export function getExportedComponentID (name: string) {
return name
}
// TODO 是否必要?用 getExportedComponentID 能否都满足
/**
* 默认导出需要有固定的名字,因为它的引用不包含它的名字信息。
*
* 例如:
* // a.san.ts
* export default class A extends Component {}
*
* // b.san.ts
* import AComponent from './a.san'
*
* // 对于如下 Component Reference,
* // 如果 id 为 AComponent 将无法定位到 a.san.ts 中的 class A
* { relativeFilePath: './a.san', id: '0', isDefault: true }
*/
export function getDefaultExportedComponentID () {
return '0'
}
7 changes: 3 additions & 4 deletions src/parsers/parse-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ function normalizeOptionTag (aNode: ANode) {
if (!aNode.children!.length) return
aNode.props.push({
name: 'value',
expr: aNode.children![0].textExpr!,
raw: ''
expr: aNode.children![0].textExpr!
})
}

Expand All @@ -50,14 +49,14 @@ function normalizeANodeProps (aNode: ANode) {

function normalizeRootClassProp (clazz: ANodeProperty) {
const parentClassExpr = clazz.expr
const expr = parseTemplate('{{class | _xclass}}').children![0].textExpr!.segs[0] as ExprInterpNode
const expr = (parseTemplate('{{class | _xclass}}').children![0].textExpr! as any).segs[0] as ExprInterpNode
expr.filters[0].args.push(parentClassExpr)
clazz.expr = expr
}

function normalizeRootStyleProp (style: ANodeProperty) {
const parentStyleExpr = style.expr
const expr = parseTemplate('{{style | _xstyle}}').children![0].textExpr!.segs[0] as ExprInterpNode
const expr = (parseTemplate('{{style | _xstyle}}').children![0].textExpr! as any).segs[0] as ExprInterpNode
expr.filters[0].args.push(parentStyleExpr)
style.expr = expr
}
Expand Down
19 changes: 12 additions & 7 deletions src/runtime/underscore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,26 @@ function includes<T> (array: T[], value: T) {
}

const HTML_ENTITY = {
/* jshint ignore:start */
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
/* eslint-disable quotes */
"'": '&#39;'
/* eslint-enable quotes */
/* jshint ignore:end */
"'": '&#39;',
'\u00a0': '&nbsp;',
'\u2003': '&emsp;',
'\u2002': '&ensp;',
'\u2009': '&thinsp;',
'\xa9': '&copy;',
'\xae': '&reg;',
'\u200c': '&zwnj;',
'\u200d': '&zwj;',
'&': '&amp;'
}
const rENTITY = new RegExp(`[${Object.keys(HTML_ENTITY).join('')}]`, 'g')

function escapeHTML (source: any) {
if (source == null) return ''
if (typeof source === 'string') {
return source.replace(/[&<>"']/g, (c: string) => HTML_ENTITY[c])
return source.replace(rENTITY, (c: string) => HTML_ENTITY[c])
}
return '' + source
}
Expand Down
29 changes: 11 additions & 18 deletions src/target-js/compilers/anode-compiler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { stringLiteralize, expr } from './expr-compiler'
import { expr } from './expr-compiler'
import { JSEmitter } from '../js-emitter'
import { ANode, ExprStringNode, AIfNode, AForNode, ASlotNode, ATemplateNode, AFragmentNode, ATextNode } from 'san'
import { ANode, AIfNode, AForNode, ASlotNode, ATemplateNode, AFragmentNode, ATextNode } from 'san'
import { ComponentInfo } from '../../models/component-info'
import { ElementCompiler } from './element-compiler'
import { stringifier } from './stringifier'
Expand Down Expand Up @@ -47,19 +47,11 @@ export class ANodeCompiler<T extends 'none' | 'typed'> {

private compileText (aNode: ATextNode) {
const { emitter } = this
if (aNode.textExpr.original && !this.ssrOnly) {
emitter.writeHTMLLiteral('<!--s-text-->')
}
const shouldEmitComment = TypeGuards.isExprTextNode(aNode.textExpr) && aNode.textExpr.original && !this.ssrOnly

if (aNode.textExpr.value != null) {
emitter.writeHTMLLiteral((aNode.textExpr.segs[0] as ExprStringNode).literal!)
} else {
emitter.writeHTMLExpression(expr(aNode.textExpr))
}

if (aNode.textExpr.original && !this.ssrOnly) {
emitter.writeHTMLLiteral('<!--/s-text-->')
}
if (shouldEmitComment) emitter.writeHTMLLiteral('<!--s-text-->')
emitter.writeHTMLExpression(expr(aNode.textExpr))
if (shouldEmitComment) emitter.writeHTMLLiteral('<!--/s-text-->')
}

compileTemplate (aNode: ATemplateNode) {
Expand Down Expand Up @@ -222,13 +214,14 @@ export class ANodeCompiler<T extends 'none' | 'typed'> {
for (const child of aNode.children!) { // nodes without children (like pATextNode) has been taken over by other methods
const slotBind = !child.textExpr && getANodePropByName(child, 'slot')
if (slotBind) {
if (!sourceSlotCodes.has(slotBind.raw)) {
sourceSlotCodes.set(slotBind.raw, {
const slotName = slotBind.expr.value
if (!sourceSlotCodes.has(slotName)) {
sourceSlotCodes.set(slotName, {
children: [],
prop: slotBind
})
}
sourceSlotCodes.get(slotBind.raw).children.push(child)
sourceSlotCodes.get(slotName).children.push(child)
} else {
defaultSourceSlots.push(child)
}
Expand Down Expand Up @@ -266,7 +259,7 @@ export class ANodeCompiler<T extends 'none' | 'typed'> {

private componentDataCode (aNode: ANode) {
const givenData = '{' + aNode.props.map(prop => {
const key = stringLiteralize(prop.name)
const key = stringifier.str(prop.name)
const val = expr(prop.expr)
return `${key}: ${val}`
}).join(', ') + '}'
Expand Down
5 changes: 3 additions & 2 deletions src/target-js/compilers/element-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { autoCloseTags } from '../../utils/dom-util'
import { ANodeCompiler } from './anode-compiler'
import { ANodeProperty, Directive, ExprType, ANode } from 'san'
import { isExprStringNode, isExprBoolNode } from '../../utils/type-guards'
import { stringifier } from './stringifier'

/**
* 编译一个 HTML 标签
Expand Down Expand Up @@ -54,7 +55,7 @@ export class ElementCompiler {
if (name === 'slot') return

if (isExprBoolNode(prop.expr)) return emitter.writeHTMLLiteral(' ' + name)
if (isExprStringNode(prop.expr)) return emitter.writeHTMLLiteral(` ${name}="${prop.expr.literal}"`)
if (isExprStringNode(prop.expr)) return emitter.writeHTMLLiteral(` ${name}=${stringifier.str(prop.expr.value)}`)
if (prop.expr.value != null) return emitter.writeHTMLLiteral(` ${name}="${expr(prop.expr)}"`)

if (name === 'value') {
Expand All @@ -81,7 +82,7 @@ export class ElementCompiler {
const valueProp = propsIndex['value']
const inputType = propsIndex['type']
if (name === 'checked' && tagName === 'input' && valueProp && inputType) {
switch (inputType.raw) {
switch (inputType.expr.value) {
case 'checkbox':
return emitter.writeIf(`_.includes(${expr(prop.expr)}, ${expr(valueProp.expr)})`, () => {
emitter.writeHTMLLiteral(' checked')
Expand Down
25 changes: 8 additions & 17 deletions src/target-js/compilers/expr-compiler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/**
* 编译源码的 helper 方法集合
*/
import { ExprNode, ExprTertiaryNode, ExprBinaryNode, ExprUnaryNode, ExprInterpNode, ExprAccessorNode, ExprCallNode, ExprTextNode, ExprObjectNode, ExprArrayNode } from 'san'
import { ExprStringNode, ExprNode, ExprTertiaryNode, ExprBinaryNode, ExprUnaryNode, ExprInterpNode, ExprAccessorNode, ExprCallNode, ExprTextNode, ExprObjectNode, ExprArrayNode } from 'san'
import { isValidIdentifier } from '../../utils/lang'
import * as TypeGuards from '../../utils/type-guards'
import { _ } from '../../runtime/underscore'

// 二元表达式操作符映射表
const binaryOp = {
Expand Down Expand Up @@ -39,15 +40,6 @@ function tertiary (e: ExprTertiaryNode) {
'?' + expr(e.segs[1]) +
':' + expr(e.segs[2])
}
// 字符串字面化
export function stringLiteralize (source: string) {
return '"' + source
.replace(/\x5C/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
.replace(/\r/g, '\\r') + '"'
}

// 生成数据访问表达式代码
export function dataAccess (accessorExpr?: ExprAccessorNode): string {
Expand Down Expand Up @@ -104,12 +96,11 @@ function interp (interpExpr: ExprInterpNode): string {
code = `ctx.instance.filters["${filterName}"].call(ctx.instance, ${code}, ${args.join(', ')})`
}
}
return interpExpr.original ? code : '_.escapeHTML(' + code + ')'
}

if (!interpExpr.original) {
return '_.escapeHTML(' + code + ')'
}

return code
function str (e: ExprStringNode): string {
return '"' + _.escapeHTML(e.value) + '"'
}

// 生成文本片段代码
Expand Down Expand Up @@ -156,7 +147,7 @@ function dispatch (e: ExprNode): string {
if (TypeGuards.isExprUnaryNode(e)) return unary(e)
if (TypeGuards.isExprBinaryNode(e)) return binary(e)
if (TypeGuards.isExprTertiaryNode(e)) return tertiary(e)
if (TypeGuards.isExprStringNode(e)) return stringLiteralize(e.literal || e.value)
if (TypeGuards.isExprStringNode(e)) return str(e)
if (TypeGuards.isExprNumberNode(e)) return '' + e.value
if (TypeGuards.isExprBoolNode(e)) return e.value ? 'true' : 'false'
if (TypeGuards.isExprAccessorNode(e)) return dataAccess(e)
Expand All @@ -166,5 +157,5 @@ function dispatch (e: ExprNode): string {
if (TypeGuards.isExprObjectNode(e)) return object(e)
if (TypeGuards.isExprCallNode(e)) return callExpr(e)
if (TypeGuards.isExprNullNode(e)) return 'null'
throw new Error(`unexpected expression ${JSON.stringify(e.raw)}`)
throw new Error(`unexpected expression ${JSON.stringify(e)}`)
}
11 changes: 7 additions & 4 deletions src/target-js/compilers/stringifier.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { stringLiteralize } from './expr-compiler'

export const stringifier = {
obj: function (source: object) {
let prefixComma
Expand All @@ -15,7 +13,7 @@ export const stringifier = {
}
prefixComma = 1

result += stringLiteralize(key) + ':' + stringifier.any(source[key])
result += stringifier.str(key) + ':' + stringifier.any(source[key])
}

return result + '}'
Expand All @@ -38,7 +36,12 @@ export const stringifier = {
},

str: function (source: string) {
return stringLiteralize(source)
return '"' + source
.replace(/\x5C/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
.replace(/\r/g, '\\r') + '"'
},

date: function (source: Date) {
Expand Down
5 changes: 3 additions & 2 deletions src/target-js/js-emitter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Emitter } from '../utils/emitter'
import { stringLiteralize, dataAccess } from './compilers/expr-compiler'
import { dataAccess } from './compilers/expr-compiler'
import { stringifier } from './compilers/stringifier'

export class JSEmitter extends Emitter {
buffer: string = ''
Expand Down Expand Up @@ -31,7 +32,7 @@ export class JSEmitter extends Emitter {
if (this.buffer === '') return
const buffer = this.buffer
this.buffer = ''
this.writeHTMLExpression(stringLiteralize(buffer))
this.writeHTMLExpression(stringifier.str(buffer))
}

public writeSwitch (expr: string, body: Function) {
Expand Down
5 changes: 2 additions & 3 deletions src/utils/anode-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ export function parseANodeProps (aNode: ANode) {
if (
TypeGuards.isExprTextNode(expr) &&
expr.segs.length === 0 &&
prop.raw == null
prop.noValue
) {
prop.expr = {
type: ExprType.BOOL,
value: true,
raw: 'true'
value: true
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/cases/html-entity/component.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

const san = require('san')

const entityStr = '&#39;&#x00021;&emsp;&ensp;&thinsp;&copy;&lt;p&gt;&reg;&lt;/p&gt;&reg;&zwnj;&zwj;&lt;&nbsp;&gt;&quot;'
const entityStr = '&#39;!&emsp;&ensp;&thinsp;&copy;&lt;p&gt;&reg;&lt;/p&gt;&reg;&zwnj;&zwj;&lt;&nbsp;&gt;&quot;'
const MyComponent = san.defineComponent({
template: '<u>' + entityStr + '</u>'
})
Expand Down
2 changes: 1 addition & 1 deletion test/cases/html-entity/expected.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<u><!--s-data:{}-->&#39;&#x00021;&emsp;&ensp;&thinsp;&copy;&lt;p&gt;&reg;&lt;/p&gt;&reg;&zwnj;&zwj;&lt;&nbsp;&gt;&quot;</u>
<u><!--s-data:{}-->&#39;!&emsp;&ensp;&thinsp;&copy;&lt;p&gt;&reg;&lt;/p&gt;&reg;&zwnj;&zwj;&lt;&nbsp;&gt;&quot;</u>
2 changes: 1 addition & 1 deletion test/unit/target-js/compilers/expr-compiler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('target-js/compilers/expr-compiler', () => {
it('should throw for unexpected expression type', () => {
const e = parseExpr('!b')
e.type = 222
expect(() => expr(e)).toThrow('unexpected expression "!b"')
expect(() => expr(e)).toThrow(/unexpected expression/)
})
it('should throw for unexpected unary operator', () => {
const e = parseExpr('!b')
Expand Down
Loading

0 comments on commit 5de18cc

Please sign in to comment.