custom-elements-ts 0.0.16 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +46 -0
- package/.github/workflows/ci.yml +49 -0
- package/.prettierrc +7 -0
- package/LICENSE +20 -0
- package/README.md +426 -168
- package/demos/counter/counter.element.html +1 -0
- package/demos/counter/counter.element.scss +234 -0
- package/demos/counter/counter.element.ts +68 -0
- package/demos/counter/index.html +205 -0
- package/demos/counter/index.ts +1 -0
- package/demos/site/code-example/code-example.element.scss +168 -0
- package/demos/site/code-example/code-example.element.ts +88 -0
- package/demos/site/event-log/event-log.element.scss +179 -0
- package/demos/site/event-log/event-log.element.ts +134 -0
- package/demos/site/favicon.svg +14 -0
- package/demos/site/index.html +346 -0
- package/demos/site/index.ts +13 -0
- package/demos/site/message/message.element.scss +75 -0
- package/demos/site/message/message.element.ts +76 -0
- package/demos/site/og-image.png +0 -0
- package/demos/site/styles/site.css +1023 -0
- package/demos/site/styles/tokens.css +56 -0
- package/demos/site/toast/toast.element.scss +110 -0
- package/demos/site/toast/toast.element.ts +63 -0
- package/demos/todo-dashboard/index.html +141 -0
- package/demos/todo-dashboard/index.ts +4 -0
- package/demos/todo-dashboard/todo-dashboard.element.scss +1145 -0
- package/demos/todo-dashboard/todo-dashboard.element.ts +332 -0
- package/demos/todo-dashboard/todo-filters.element.ts +54 -0
- package/demos/todo-dashboard/todo-item.element.ts +126 -0
- package/demos/todo-dashboard/todo-stats.element.ts +189 -0
- package/package.json +73 -29
- package/src/custom-element.ts +206 -0
- package/{index.d.ts → src/index.ts} +2 -0
- package/src/listen.ts +70 -0
- package/src/prop.ts +92 -0
- package/src/state.ts +129 -0
- package/src/template-runtime.ts +435 -0
- package/src/toggle.ts +66 -0
- package/src/tsconfig.json +24 -0
- package/src/util.ts +33 -0
- package/src/watch.ts +14 -0
- package/tests/basic.spec.ts +70 -0
- package/tests/custom-element.spec.ts +77 -0
- package/tests/dispatch.spec.ts +52 -0
- package/tests/init.spec.ts +94 -0
- package/tests/listen.spec.ts +118 -0
- package/tests/prop.spec.ts +118 -0
- package/tests/templating-runtime.spec.ts +575 -0
- package/tests/toggle.spec.ts +92 -0
- package/tests/watch.spec.ts +183 -0
- package/tools/build.js +119 -0
- package/tools/bundle.js +167 -0
- package/tools/rollup-config.js +70 -0
- package/tools/start.js +188 -0
- package/tsconfig.json +38 -0
- package/vite.config.mts +30 -0
- package/bundles/custom-elements-ts.umd.js +0 -315
- package/bundles/custom-elements-ts.umd.js.map +0 -1
- package/custom-element.d.ts +0 -12
- package/esm2015/custom-elements-ts.js +0 -260
- package/esm2015/custom-elements-ts.js.map +0 -1
- package/esm5/custom-elements-ts.js +0 -298
- package/esm5/custom-elements-ts.js.map +0 -1
- package/listen.d.ts +0 -17
- package/prop.d.ts +0 -2
- package/toggle.d.ts +0 -1
- package/util.d.ts +0 -4
- package/watch.d.ts +0 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { CustomElement, Prop, Toggle, Watch } from 'custom-elements-ts';
|
|
3
|
+
|
|
4
|
+
@CustomElement({
|
|
5
|
+
tag: 'watch-element',
|
|
6
|
+
template: '<span>my element</span>',
|
|
7
|
+
style: ':host{border:0}',
|
|
8
|
+
})
|
|
9
|
+
class WatchElement extends HTMLElement {
|
|
10
|
+
@Prop() name: any;
|
|
11
|
+
|
|
12
|
+
@Watch('name')
|
|
13
|
+
setSpan(value: any) {
|
|
14
|
+
const span = this.shadowRoot!.querySelector('span');
|
|
15
|
+
span!.innerHTML = value.new;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set label(value: any) {
|
|
19
|
+
this.setAttribute('label', value);
|
|
20
|
+
}
|
|
21
|
+
get label(): any {
|
|
22
|
+
return this.getAttribute('label') || '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
newLabel = '';
|
|
26
|
+
@Watch('label')
|
|
27
|
+
setLabel(value: any) {
|
|
28
|
+
this.newLabel = value.new;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
newColor = '';
|
|
32
|
+
@Prop() color = 'blue';
|
|
33
|
+
initialColor = '';
|
|
34
|
+
@Watch('color')
|
|
35
|
+
changeColor(value: any) {
|
|
36
|
+
this.initialColor ||= value.new;
|
|
37
|
+
this.newColor = this.color;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
caseChanged = false;
|
|
41
|
+
@Prop() setCase: any;
|
|
42
|
+
@Watch('setCase')
|
|
43
|
+
changeCase(value: any) {
|
|
44
|
+
this.caseChanged = this.setCase === value.new;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Watch('set-kebab')
|
|
48
|
+
changeKebabCase(value: any) {
|
|
49
|
+
this.caseChanged = this.setCase === value.new;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
menuChanged = false;
|
|
53
|
+
@Prop() menus = 'a';
|
|
54
|
+
@Watch('menus')
|
|
55
|
+
changeMenus(value: any) {
|
|
56
|
+
this.menuChanged = this.menus === value.new;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
enabledChanged = false;
|
|
60
|
+
@Toggle() enabled: any;
|
|
61
|
+
@Watch('enabled')
|
|
62
|
+
changeEnable(value: any) {
|
|
63
|
+
this.enabledChanged = this.enabled.toString() === value.new;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe('watch decorator', () => {
|
|
68
|
+
let myElementInstance: any;
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
const myElement = document.createElement('watch-element');
|
|
72
|
+
myElementInstance = document.body.appendChild(myElement);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
document.body.innerHTML = '';
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should re-render setting property', () => {
|
|
80
|
+
myElementInstance.name = 'Aivan';
|
|
81
|
+
expect(myElementInstance.shadowRoot.querySelector('span').innerHTML).toEqual('Aivan');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should call method decorated with @Watch on prop change', () => {
|
|
85
|
+
const watchSpy = vi.spyOn(myElementInstance, 'setSpan');
|
|
86
|
+
myElementInstance.name = 'Aivan';
|
|
87
|
+
expect(watchSpy).toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should call method decorated with @Watch on prop change', () => {
|
|
91
|
+
const watchSpy = vi.spyOn(myElementInstance, 'setSpan');
|
|
92
|
+
myElementInstance.setAttribute('name', 'Mario');
|
|
93
|
+
expect(watchSpy).toHaveBeenCalledWith(...[{ old: null, new: 'Mario' }]);
|
|
94
|
+
expect(myElementInstance.name).toEqual('Mario');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should call @Watch on attribute change with new property value', () => {
|
|
98
|
+
myElementInstance.setAttribute('color', 'red');
|
|
99
|
+
expect(myElementInstance.newColor).toEqual('red');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should call @Watch for initial kebab-resolved default prop value', () => {
|
|
103
|
+
expect(myElementInstance.initialColor).toEqual('blue');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should call @Watch for plain get/set with correct value.new', () => {
|
|
107
|
+
myElementInstance.setAttribute('label', 'Name');
|
|
108
|
+
expect(myElementInstance.newLabel).toEqual('Name');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should call non kebab @Watch on kebab attribute change with new property value', () => {
|
|
112
|
+
myElementInstance.setAttribute('set-case', 'kebab');
|
|
113
|
+
expect(myElementInstance.setCase).toEqual('kebab');
|
|
114
|
+
expect(myElementInstance.caseChanged).toBeTruthy();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should call kebab @Watch on kebab attribute change with new property value', () => {
|
|
118
|
+
myElementInstance.setAttribute('set-case', 'snake');
|
|
119
|
+
expect(myElementInstance.setCase).toEqual('snake');
|
|
120
|
+
expect(myElementInstance.caseChanged).toBeTruthy();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should call @Watch on property change with new property value', () => {
|
|
124
|
+
const menus = [
|
|
125
|
+
{
|
|
126
|
+
text: 'Colors',
|
|
127
|
+
link: '/user-interface/style-guides/colors',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
text: 'Logo',
|
|
131
|
+
link: '/user-interface/style-guides/logo',
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
myElementInstance.menus = menus;
|
|
135
|
+
expect(myElementInstance.menus).toEqual(menus);
|
|
136
|
+
expect(myElementInstance.menuChanged).toBeTruthy();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should call @Watch on toggle attribute with new property value', () => {
|
|
140
|
+
myElementInstance.enabled = true;
|
|
141
|
+
expect(myElementInstance.enabledChanged).toBeTruthy();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should register watch metadata on the constructor for all watched attributes', () => {
|
|
145
|
+
const meta = (WatchElement as any).watchAttributes as Record<string, string>;
|
|
146
|
+
const propsInit = (WatchElement as any).propsInit as Record<string, unknown>;
|
|
147
|
+
|
|
148
|
+
expect(meta['name']).toBe('setSpan');
|
|
149
|
+
expect(meta['label']).toBe('setLabel');
|
|
150
|
+
expect(meta['color']).toBe('changeColor');
|
|
151
|
+
// camelCase key is kebab-cased in metadata
|
|
152
|
+
expect(meta['set-case']).toBe('changeCase');
|
|
153
|
+
// kebab key is preserved
|
|
154
|
+
expect(meta['set-kebab']).toBe('changeKebabCase');
|
|
155
|
+
expect(meta['menus']).toBe('changeMenus');
|
|
156
|
+
expect(meta['enabled']).toBe('changeEnable');
|
|
157
|
+
|
|
158
|
+
// propsInit entries are created (null placeholders)
|
|
159
|
+
['name', 'label', 'color', 'setCase', 'set-kebab', 'menus', 'enabled'].forEach((k) => {
|
|
160
|
+
expect(propsInit).toHaveProperty(k);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Ensure subsequent @Watch registrations on the same attribute update the mapping (last one wins)
|
|
166
|
+
@CustomElement({
|
|
167
|
+
tag: 'dupe-watch-element',
|
|
168
|
+
template: '<span>dupe</span>',
|
|
169
|
+
})
|
|
170
|
+
class DupeWatchElement extends HTMLElement {
|
|
171
|
+
@Watch('foo')
|
|
172
|
+
first(_v: any) {}
|
|
173
|
+
|
|
174
|
+
@Watch('foo')
|
|
175
|
+
second(_v: any) {}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
describe('watch decorator duplicate attribute handling', () => {
|
|
179
|
+
it('last @Watch for same attribute should win in metadata map', () => {
|
|
180
|
+
const meta = (DupeWatchElement as any).watchAttributes as Record<string, string>;
|
|
181
|
+
expect(meta['foo']).toBe('second');
|
|
182
|
+
});
|
|
183
|
+
});
|
package/tools/build.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const { existsSync, mkdirSync, copyFileSync } = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { config, ELEMENT_NAME } = require('./rollup-config');
|
|
5
|
+
const rollup = require('rollup');
|
|
6
|
+
const rimraf = require('rimraf');
|
|
7
|
+
const { copyFile } = require('fs').promises;
|
|
8
|
+
const glob = require('glob');
|
|
9
|
+
const { readFileSync, writeFileSync } = require('fs');
|
|
10
|
+
|
|
11
|
+
const DEST_PATH = 'dist';
|
|
12
|
+
// Inline every demo source so a demo (like `site`) can freely import sibling
|
|
13
|
+
// demos and have their templateUrl/styleUrl references resolved.
|
|
14
|
+
const SRC_PATH = `demos/**/*.ts`;
|
|
15
|
+
const SRC_TMP_PATH = `.tmp`;
|
|
16
|
+
|
|
17
|
+
const STATIC_ASSET_EXTS = new Set([
|
|
18
|
+
'.css', '.svg', '.png', '.jpg', '.jpeg', '.gif',
|
|
19
|
+
'.ico', '.webp', '.woff', '.woff2', '.ttf', '.otf'
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
function copyStaticAssets(srcDir, destDir) {
|
|
23
|
+
const files = glob.sync(`${srcDir}/**/*`, { nodir: true });
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
const ext = path.extname(file).toLowerCase();
|
|
26
|
+
if (!STATIC_ASSET_EXTS.has(ext)) continue;
|
|
27
|
+
const rel = path.relative(srcDir, file);
|
|
28
|
+
const dest = path.join(destDir, rel);
|
|
29
|
+
const destSubDir = path.dirname(dest);
|
|
30
|
+
if (!existsSync(destSubDir)) mkdirSync(destSubDir, { recursive: true });
|
|
31
|
+
copyFileSync(file, dest);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function copyDemoShell() {
|
|
36
|
+
if (!existsSync(DEST_PATH)) mkdirSync(DEST_PATH, { recursive: true });
|
|
37
|
+
const indexSrc = `demos/${ELEMENT_NAME}/index.html`;
|
|
38
|
+
if (existsSync(indexSrc)) {
|
|
39
|
+
copyFileSync(indexSrc, path.join(DEST_PATH, 'index.html'));
|
|
40
|
+
}
|
|
41
|
+
copyStaticAssets(`demos/${ELEMENT_NAME}`, DEST_PATH);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function clean(dir) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
rimraf(dir, (err) => {
|
|
47
|
+
if (err) reject(err);
|
|
48
|
+
else resolve();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function inlineSources(srcPattern, destPath) {
|
|
54
|
+
const files = glob.sync(srcPattern);
|
|
55
|
+
|
|
56
|
+
if (!existsSync(destPath)) {
|
|
57
|
+
mkdirSync(destPath, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
let content = readFileSync(file, 'utf-8');
|
|
62
|
+
const relativePath = path.relative('demos', file);
|
|
63
|
+
const destFile = path.join(destPath, relativePath);
|
|
64
|
+
const destDir = path.dirname(destFile);
|
|
65
|
+
|
|
66
|
+
if (!existsSync(destDir)) {
|
|
67
|
+
mkdirSync(destDir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Inline templateUrl and styleUrl references
|
|
71
|
+
content = inlineTemplates(content, path.dirname(file));
|
|
72
|
+
|
|
73
|
+
writeFileSync(destFile, content);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function inlineTemplates(content, basePath) {
|
|
78
|
+
// Match templateUrl: './file.html'
|
|
79
|
+
const templateUrlRegex = /templateUrl:\s*['"`]([^'"`]+)['"`]/g;
|
|
80
|
+
content = content.replace(templateUrlRegex, (match, filePath) => {
|
|
81
|
+
const fullPath = path.join(basePath, filePath);
|
|
82
|
+
if (existsSync(fullPath)) {
|
|
83
|
+
const template = readFileSync(fullPath, 'utf-8')
|
|
84
|
+
.replace(/`/g, '\\`')
|
|
85
|
+
.replace(/\$/g, '\\$');
|
|
86
|
+
return `template: \`${template}\``;
|
|
87
|
+
}
|
|
88
|
+
return match;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Match styleUrl: './file.scss' or './file.css'
|
|
92
|
+
const styleUrlRegex = /styleUrl:\s*['"`]([^'"`]+)['"`]/g;
|
|
93
|
+
content = content.replace(styleUrlRegex, (match, filePath) => {
|
|
94
|
+
const fullPath = path.join(basePath, filePath);
|
|
95
|
+
if (existsSync(fullPath)) {
|
|
96
|
+
let style = readFileSync(fullPath, 'utf-8');
|
|
97
|
+
// For SCSS files, we'd normally compile them, but for now just use as CSS
|
|
98
|
+
style = style.replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
99
|
+
return `style: \`${style}\``;
|
|
100
|
+
}
|
|
101
|
+
return match;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return content;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function rollupGenerate(config) {
|
|
108
|
+
const bundle = await rollup.rollup(config.inputOptions);
|
|
109
|
+
await bundle.write(config.outputOptions);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Promise.all([clean(DEST_PATH), clean(SRC_TMP_PATH)])
|
|
113
|
+
.then(() => inlineSources(SRC_PATH, SRC_TMP_PATH))
|
|
114
|
+
.then(() => rollupGenerate(config))
|
|
115
|
+
.then(() => copyDemoShell())
|
|
116
|
+
.catch(err => {
|
|
117
|
+
console.error('Build failed:', err);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|
package/tools/bundle.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const rollup = require('rollup');
|
|
3
|
+
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
|
4
|
+
const typescript = require('rollup-plugin-typescript2');
|
|
5
|
+
const ts = require('typescript');
|
|
6
|
+
const rimraf = require('rimraf');
|
|
7
|
+
const { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync } = require('fs');
|
|
8
|
+
const MagicString = require('magic-string');
|
|
9
|
+
|
|
10
|
+
const LIB_NAME = 'custom-elements-ts';
|
|
11
|
+
|
|
12
|
+
// removing custom comment-strip plugin; modern toolchain handles comments safely
|
|
13
|
+
|
|
14
|
+
const createConfig = () => {
|
|
15
|
+
return ['umd', 'esm5', 'esm2015'].map((format) => {
|
|
16
|
+
const tsConfig = {
|
|
17
|
+
compilerOptions: {
|
|
18
|
+
target: format.includes('esm2015') ? 'es2015' : 'es5',
|
|
19
|
+
declaration: false,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const file = format.includes('umd')
|
|
24
|
+
? path.join('dist', 'bundles', `${LIB_NAME}.umd.js`)
|
|
25
|
+
: path.join('dist', format, `${LIB_NAME}.js`);
|
|
26
|
+
|
|
27
|
+
const formatType = format.includes('umd') ? 'umd' : 'es';
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
inputOptions: {
|
|
31
|
+
treeshake: true,
|
|
32
|
+
input: 'src/index.ts',
|
|
33
|
+
plugins: [
|
|
34
|
+
typescript({
|
|
35
|
+
tsconfig: 'src/tsconfig.json',
|
|
36
|
+
tsconfigOverride: { ...tsConfig },
|
|
37
|
+
check: false,
|
|
38
|
+
cacheRoot: path.join(path.resolve(), 'node_modules/.tmp/.rts2_cache'),
|
|
39
|
+
// ensure tslib helpers are injected correctly for ES5 target
|
|
40
|
+
tslib: require.resolve('tslib'),
|
|
41
|
+
}),
|
|
42
|
+
nodeResolve(),
|
|
43
|
+
],
|
|
44
|
+
onwarn(warning) {
|
|
45
|
+
if (warning.code === 'THIS_IS_UNDEFINED') {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log('Rollup warning: ', warning.message);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
outputOptions: {
|
|
52
|
+
sourcemap: true,
|
|
53
|
+
file: file,
|
|
54
|
+
name: LIB_NAME,
|
|
55
|
+
format: formatType,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
async function clean(dir) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
rimraf(dir, (err) => {
|
|
64
|
+
if (err) reject(err);
|
|
65
|
+
else resolve();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function rollupBuild(config) {
|
|
71
|
+
const bundle = await rollup.rollup(config.inputOptions);
|
|
72
|
+
await bundle.write(config.outputOptions);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const emitDtsFiles = () => {
|
|
76
|
+
const configPath = path.resolve('src', 'tsconfig.json');
|
|
77
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
78
|
+
if (configFile.error) {
|
|
79
|
+
throw new Error(ts.formatDiagnosticsWithColorAndContext([configFile.error], formatHost));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const parsedConfig = ts.parseJsonConfigFileContent(
|
|
83
|
+
configFile.config,
|
|
84
|
+
ts.sys,
|
|
85
|
+
path.resolve('src'),
|
|
86
|
+
{
|
|
87
|
+
declaration: true,
|
|
88
|
+
declarationDir: path.resolve('dist'),
|
|
89
|
+
emitDeclarationOnly: true,
|
|
90
|
+
outDir: path.resolve('dist'),
|
|
91
|
+
rootDir: path.resolve('src'),
|
|
92
|
+
sourceMap: false,
|
|
93
|
+
},
|
|
94
|
+
configPath
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const program = ts.createProgram(parsedConfig.fileNames, parsedConfig.options);
|
|
98
|
+
const emitResult = program.emit();
|
|
99
|
+
const diagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
|
|
100
|
+
const errors = diagnostics.filter(
|
|
101
|
+
(diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error
|
|
102
|
+
);
|
|
103
|
+
if (errors.length > 0) {
|
|
104
|
+
throw new Error(ts.formatDiagnosticsWithColorAndContext(errors, formatHost));
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const formatHost = {
|
|
109
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
110
|
+
getCurrentDirectory: () => process.cwd(),
|
|
111
|
+
getNewLine: () => '\n',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const copyReadMe = () => {
|
|
115
|
+
if (existsSync('README.md')) {
|
|
116
|
+
copyFileSync('README.md', path.join('dist', 'README.md'));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const buildCopyPackageFile = (libName, paths) => {
|
|
121
|
+
const pkg = JSON.parse(readFileSync('package.json', 'utf-8'));
|
|
122
|
+
const distPkg = {
|
|
123
|
+
name: libName,
|
|
124
|
+
version: pkg.version,
|
|
125
|
+
description: pkg.description,
|
|
126
|
+
main: paths.main,
|
|
127
|
+
module: paths.module,
|
|
128
|
+
esm5: paths.esm5,
|
|
129
|
+
esm2015: paths.esm2015,
|
|
130
|
+
typings: paths.typings,
|
|
131
|
+
peerDependencies: pkg.peerDependencies,
|
|
132
|
+
repository: pkg.repository,
|
|
133
|
+
keywords: pkg.keywords,
|
|
134
|
+
author: pkg.author,
|
|
135
|
+
license: pkg.license,
|
|
136
|
+
bugs: pkg.bugs,
|
|
137
|
+
homepage: pkg.homepage,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (!existsSync('dist')) {
|
|
141
|
+
mkdirSync('dist', { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!existsSync('dist/bundles')) {
|
|
145
|
+
mkdirSync('dist/bundles', { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
writeFileSync(path.join('dist', 'package.json'), JSON.stringify(distPkg, null, 2));
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const copyPkgFile = () =>
|
|
152
|
+
buildCopyPackageFile(LIB_NAME, {
|
|
153
|
+
main: `./bundles/${LIB_NAME}.umd.js`,
|
|
154
|
+
esm5: `./esm5/${LIB_NAME}.js`,
|
|
155
|
+
module: `./esm2015/${LIB_NAME}.js`,
|
|
156
|
+
esm2015: `./esm2015/${LIB_NAME}.js`,
|
|
157
|
+
typings: 'index.d.ts',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
Promise.all([clean('dist'), clean('.tmp')])
|
|
161
|
+
.then(() => Promise.all(createConfig().map((config) => rollupBuild(config))))
|
|
162
|
+
.then(() => emitDtsFiles())
|
|
163
|
+
.then(() => Promise.all([copyPkgFile(), copyReadMe()]))
|
|
164
|
+
.catch((err) => {
|
|
165
|
+
console.error('Bundle failed:', err);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const typescript = require('rollup-plugin-typescript2');
|
|
3
|
+
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
|
4
|
+
const terserPlugin = require('@rollup/plugin-terser');
|
|
5
|
+
const terser = terserPlugin.terser || terserPlugin;
|
|
6
|
+
const { existsSync } = require('fs');
|
|
7
|
+
|
|
8
|
+
const ELEMENT_NAME = process.argv[2];
|
|
9
|
+
const DEST_PATH = 'dist';
|
|
10
|
+
|
|
11
|
+
const prodModeParams = ['--prod', '--prod=true', '--prod true'];
|
|
12
|
+
|
|
13
|
+
const ELEMENT_PATH = `${ELEMENT_NAME}/index.ts`;
|
|
14
|
+
const INPUT_PATH = path.join('.tmp', ELEMENT_PATH);
|
|
15
|
+
|
|
16
|
+
if (ELEMENT_NAME == undefined) {
|
|
17
|
+
console.log('specify which element to start');
|
|
18
|
+
console.log(' ↳ eg. npm start element-name');
|
|
19
|
+
process.exit();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!existsSync(`demos/${ELEMENT_PATH}`)) {
|
|
23
|
+
console.log('element does not exist');
|
|
24
|
+
process.exit();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const toPascalCase = (text) => {
|
|
28
|
+
return text.replace(/-\w/g, m => m[1].toUpperCase())
|
|
29
|
+
.replace(/^\w/, c => c.toUpperCase());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isProcess(params) {
|
|
33
|
+
return params.some(param => process.argv.includes(param));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const config = {
|
|
37
|
+
inputOptions: {
|
|
38
|
+
treeshake: true,
|
|
39
|
+
input: INPUT_PATH,
|
|
40
|
+
plugins: [
|
|
41
|
+
typescript({
|
|
42
|
+
useTsconfigDeclarationDir: true,
|
|
43
|
+
check: false,
|
|
44
|
+
cacheRoot: path.join(path.resolve(), 'node_modules/.tmp/.rts2_cache')
|
|
45
|
+
}),
|
|
46
|
+
nodeResolve()
|
|
47
|
+
],
|
|
48
|
+
onwarn(warning) {
|
|
49
|
+
if (warning.code === 'THIS_IS_UNDEFINED') { return; }
|
|
50
|
+
console.log("Rollup warning: ", warning.message);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
outputOptions: {
|
|
54
|
+
sourcemap: true,
|
|
55
|
+
exports: 'named',
|
|
56
|
+
name: toPascalCase(ELEMENT_NAME),
|
|
57
|
+
file: `${DEST_PATH}/${ELEMENT_NAME}.umd.js`,
|
|
58
|
+
format: 'umd'
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (isProcess(prodModeParams)) {
|
|
63
|
+
const options = {
|
|
64
|
+
mangle: { keep_fnames: true }
|
|
65
|
+
};
|
|
66
|
+
config.inputOptions.plugins.push(terser(options));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
exports.config = config;
|
|
70
|
+
exports.ELEMENT_NAME = ELEMENT_NAME;
|