Skip to content

Commit

Permalink
Merge commit '9933ec6' into localize-msgdesc
Browse files Browse the repository at this point in the history
  • Loading branch information
aomarks committed Dec 4, 2020
2 parents 11a932f + 9933ec6 commit 8b2643e
Show file tree
Hide file tree
Showing 35 changed files with 389 additions and 160 deletions.
12 changes: 6 additions & 6 deletions packages/benchmarks/.gitignore
@@ -1,9 +1,9 @@
generated/
generator/build/
generator/generated/
!generator/types/deoptigate.d.ts
!lit-html/template-heavy/template-heavy.js
node_modules/
/generated/
/generator/build/
/generator/generated/
!/generator/types/deoptigate.d.ts
!/lit-html/template-heavy/template-heavy.js
/node_modules/
**/*.d.ts
**/*.d.ts.map
**/*.js
Expand Down
6 changes: 3 additions & 3 deletions packages/labs/.gitignore
@@ -1,3 +1,3 @@
development/
node_modules/
index.*
/development/
/node_modules/
/index.*
26 changes: 13 additions & 13 deletions packages/lit-element/.gitignore
@@ -1,13 +1,13 @@
decorators/
demo/**/*.d.ts
demo/**/*.d.ts.map
demo/**/*.js
demo/**/*.js.map
development/
node_modules/
test/
decorators.*
hydrate-support.*
lit-element.*
lit.min.*
platform-support.*
/decorators/
/demo/**/*.d.ts
/demo/**/*.d.ts.map
/demo/**/*.js
/demo/**/*.js.map
/development/
/node_modules/
/test/
/decorators.*
/hydrate-support.*
/lit-element.*
/lit.min.*
/platform-support.*
18 changes: 9 additions & 9 deletions packages/lit-html/.gitignore
@@ -1,9 +1,9 @@
development/
directives/
node_modules/
test/
hydrate.*
lit-html.*
parts.*
platform-support.*
private-ssr-support.*
/development/
/directives/
/node_modules/
/test/
/hydrate.*
/lit-html.*
/parts.*
/platform-support.*
/private-ssr-support.*
3 changes: 3 additions & 0 deletions packages/lit-html/CHANGELOG.md
Expand Up @@ -34,6 +34,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `renderBefore` to render options. If specified, content is rendered before the node given via render options, e.g. `{renderBefore: node}`.
- Added development mode, which can be enabled by setting the `development` Node exports condition. See `README.md` for more details.

* Added `unsafeStatic()`, which allows template authors to add strings to the
static structure of the template, before it's parsed as HTML.

### Fixed

- All usage of `instanceof` has been removed, making rendering more likely to
Expand Down
1 change: 1 addition & 0 deletions packages/lit-html/rollup.config.js
Expand Up @@ -30,6 +30,7 @@ export default litProdConfig({
'directives/render-light',
'lit-html',
'parts',
'static',
'hydrate',
'private-ssr-support',
'platform-support',
Expand Down
102 changes: 102 additions & 0 deletions packages/lit-html/src/static.ts
@@ -0,0 +1,102 @@
/**
* @license
* Copyright (c) 2020 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
import {html as coreHtml, svg as coreSvg, TemplateResult} from './lit-html.js';

/**
* Wraps a string so that it behaves like part of the static template
* strings instead of a dynamic value.
*
* This is a very unsafe operation and may break templates if changes
* the structure of a template. Do not pass user input to this function
* without sanitizing it.
*
* Static values can be changed, but they will cause a complete re-render
* since they effectively create a new template.
*/
export const unsafeStatic = (value: string) => ({
_$litStatic$: value,
});

type StaticValue = ReturnType<typeof unsafeStatic>;

const stringsCache = new Map<string, TemplateStringsArray>();

/**
* Wraps a lit-html template tag (`html` or `svg`) to add static value support.
*/
export const withStatic = (coreTag: typeof coreHtml) => (
strings: TemplateStringsArray,
...values: unknown[]
): TemplateResult => {
const l = values.length;
let staticValue: string | undefined;
let dynamicValue: unknown;
const staticStrings: Array<string> = [];
const dynamicValues: Array<unknown> = [];
let i = 0;
let hasStatics = false;
let s: string;

while (i < l) {
s = strings[i];
// Collect any unsafeStatic values, and their following template strings
// so that we treat a run of template strings and unsafe static values as
// a single template string.
while (
i < l &&
((dynamicValue = values[i]),
(staticValue = (dynamicValue as StaticValue)?._$litStatic$)) !== undefined
) {
s += staticValue + strings[++i];
hasStatics = true;
}
dynamicValues.push(dynamicValue);
staticStrings.push(s);
i++;
}
// If the last value isn't static (which would have consumed the last
// string), then we need to add the last string.
if (i === l) {
staticStrings.push(strings[l]);
}

if (hasStatics) {
const key = staticStrings.join('$$lit$$');
strings = stringsCache.get(key)!;
if (strings === undefined) {
stringsCache.set(
key,
(strings = (staticStrings as unknown) as TemplateStringsArray)
);
}
values = dynamicValues;
}
return coreTag(strings, ...values);
};

/**
* Interprets a template literal as an HTML template that can efficiently
* render to and update a container.
*
* Includes static value support from `lit-html/static.js`.
*/
export const html = withStatic(coreHtml);

/**
* Interprets a template literal as an SVG template that can efficiently
* render to and update a container.
*
* Includes static value support from `lit-html/static.js`.
*/
export const svg = withStatic(coreSvg);
2 changes: 1 addition & 1 deletion packages/lit-html/src/test/lit-html_test.ts
Expand Up @@ -20,9 +20,9 @@ import {
NodePart,
nothing,
render,
RenderOptions,
svg,
TemplateResult,
RenderOptions,
SanitizerFactory,
} from '../lit-html.js';
import {assert} from '@esm-bundle/chai';
Expand Down
137 changes: 137 additions & 0 deletions packages/lit-html/src/test/static_test.ts
@@ -0,0 +1,137 @@
/**
* @license
* Copyright (c) 2020 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
import {render} from '../lit-html.js';
import {html, unsafeStatic} from '../static.js';
import {assert} from '@esm-bundle/chai';
import {stripExpressionComments} from './test-utils/strip-markers.js';

suite('static', () => {
let container: HTMLElement;

setup(() => {
container = document.createElement('div');
});

test('static text binding', () => {
render(html`${unsafeStatic('<p>Hello</p>')}`, container);
// If this were a dynamic binding, the tags would be escaped
assert.equal(stripExpressionComments(container.innerHTML), '<p>Hello</p>');
});

test('static attribute binding', () => {
render(html`<div class="${unsafeStatic('cool')}"></div>`, container);
assert.equal(
stripExpressionComments(container.innerHTML),
'<div class="cool"></div>'
);
// TODO: test that this is actually static. It's not currently possible with
// the public API
});

test('static tag binding', () => {
const tagName = unsafeStatic('div');
render(html`<${tagName}>${'A'}</${tagName}>`, container);
assert.equal(stripExpressionComments(container.innerHTML), '<div>A</div>');
});

test('static attribute name binding', () => {
render(html`<div ${unsafeStatic('foo')}="${'bar'}"></div>`, container);
assert.equal(
stripExpressionComments(container.innerHTML),
'<div foo="bar"></div>'
);

render(html`<div x-${unsafeStatic('foo')}="${'bar'}"></div>`, container);
assert.equal(
stripExpressionComments(container.innerHTML),
'<div x-foo="bar"></div>'
);
});

test('static attribute name binding', () => {
render(
html`<div ${unsafeStatic('foo')}="${unsafeStatic('bar')}"></div>`,
container
);
assert.equal(
stripExpressionComments(container.innerHTML),
'<div foo="bar"></div>'
);
});

test('dynamic binding after static text binding', () => {
render(html`${unsafeStatic('<p>Hello</p>')}${'<p>World</p>'}`, container);
assert.equal(
stripExpressionComments(container.innerHTML),
'<p>Hello</p>&lt;p&gt;World&lt;/p&gt;'
);

// Make sure `null` is handled
render(html`${unsafeStatic('<p>Hello</p>')}${null}`, container);
assert.equal(stripExpressionComments(container.innerHTML), '<p>Hello</p>');
});

test('static bindings are keyed by static values', () => {
// A template with a bound tag name. We should be able to re-render
// this template with different tag names and have the tag names update.
// New tag names will act as different templates.
const t = (tag: string, text: string) =>
html`<${unsafeStatic(tag)}>${text}</${unsafeStatic(tag)}>`;

render(t('div', 'abc'), container);
assert.equal(
stripExpressionComments(container.innerHTML),
'<div>abc</div>'
);
const div = container.querySelector('div');
assert.isNotNull(div);

render(t('div', 'def'), container);
assert.equal(
stripExpressionComments(container.innerHTML),
'<div>def</div>'
);
const div2 = container.querySelector('div');
// Static values are stable between renders like static template strings
assert.strictEqual(div2, div);

render(t('span', 'abc'), container);
// Rendering with a new static value should work, though it re-renders
// since we have a new template.
assert.equal(
stripExpressionComments(container.innerHTML),
'<span>abc</span>'
);
const span = container.querySelector('span');
assert.isNotNull(span);

render(t('span', 'def'), container);
assert.equal(
stripExpressionComments(container.innerHTML),
'<span>def</span>'
);
const span2 = container.querySelector('span');
assert.strictEqual(span2, span);

render(t('div', 'abc'), container);
assert.equal(
stripExpressionComments(container.innerHTML),
'<div>abc</div>'
);
const div3 = container.querySelector('div');
// Static values do not have any caching behavior. Re-rendering with a
// previously used value does not restore static DOM
assert.notStrictEqual(div3, div);
});
});
10 changes: 5 additions & 5 deletions packages/lit-ssr/.gitignore
@@ -1,5 +1,5 @@
demo/
lib/
node_modules/
test/
index.*
/demo/
/lib/
/node_modules/
/test/
/index.*
18 changes: 9 additions & 9 deletions packages/localize/.gitignore
@@ -1,11 +1,11 @@
lib/
lib_client/
node_modules/
testdata/*/output/
tests/
.tsbuildinfo.client
lit-localize.*
localized-element.*
examples/*/node_modules/
/lib/
/lib_client/
/node_modules/
/testdata/*/output/
/tests/
/.tsbuildinfo.client
/lit-localize.*
/localized-element.*
/examples/*/node_modules/
/fnv1a64.*
/id-generation.*
6 changes: 3 additions & 3 deletions packages/localize/README.md
Expand Up @@ -40,7 +40,7 @@ Run `lit-localize` to extract all localizable templates and generate an XLIFF
file, a format which is supported by many localization tools and services:

```xml
<trans-unit id="0h3c44aff2d5f5ef6b">
<trans-unit id="ah3c44aff2d5f5ef6b">
<source>Hello <ph id="0">&lt;b></ph>World<ph id="1">&lt;/b></ph>!</source>
<!-- target tag added by your localization process -->
<target>Hola <ph id="0">&lt;b></ph>Mundo<ph id="1">&lt;/b></ph>!</target>
Expand Down Expand Up @@ -165,7 +165,7 @@ lit-localize supports two output modes: _transform_ and _runtime_.
into `<ph>` tags.

```xml
<trans-unit id="0h3c44aff2d5f5ef6b">
<trans-unit id="ah3c44aff2d5f5ef6b">
<source>Hello <ph id="0">&lt;b></ph>World<ph id="1">&lt;/b></ph>!</source>
</trans-unit>
```
Expand All @@ -175,7 +175,7 @@ lit-localize supports two output modes: _transform_ and _runtime_.
this tag by feeding it this XLIFF file.

```xml
<trans-unit id="0h3c44aff2d5f5ef6b">
<trans-unit id="ah3c44aff2d5f5ef6b">
<source>Hello <ph id="0">&lt;b></ph>World<ph id="1">&lt;/b></ph>!</source>
<target>Hola <ph id="0">&lt;b></ph>Mundo<ph id="1">&lt;/b></ph>!</target>
</trans-unit>
Expand Down

0 comments on commit 8b2643e

Please sign in to comment.