create-dalila 1.2.14 → 1.2.15

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/README.md CHANGED
@@ -23,11 +23,14 @@ Open http://localhost:4242 to see your app.
23
23
  - Dev server + route generation watcher
24
24
  - TypeScript support out of the box
25
25
  - Minimal CSS styling
26
+ - `dompurify` installed and wired into the runtime bootstrap
27
+ - Trusted Types enabled in the starter runtime config
26
28
 
27
29
  ## Project Structure
28
30
 
29
31
  ```
30
32
  my-app/
33
+ ├── build.mjs # Packages a standalone dist/ preview build
31
34
  ├── dev.mjs # Runs route watcher + dev server
32
35
  ├── index.html # App shell
33
36
  ├── src/
@@ -35,7 +38,7 @@ my-app/
35
38
  │ │ ├── layout.html
36
39
  │ │ ├── page.html
37
40
  │ │ └── page.ts
38
- │ ├── main.ts # Router bootstrap
41
+ │ ├── main.ts # Router bootstrap + runtime security defaults
39
42
  │ └── style.css # Styles
40
43
  ├── package.json
41
44
  └── tsconfig.json
@@ -46,7 +49,8 @@ my-app/
46
49
  - `npm run dev` - Start dev server and route watcher
47
50
  - `npm run routes` - Generate route files once
48
51
  - `npm run routes:watch` - Watch route files and regenerate outputs
49
- - `npm run build` - Compile TypeScript
52
+ - `npm run build` - Generate routes, compile TypeScript, and package a standalone `dist/`
53
+ - `npm run preview` - Serve the built `dist/` output locally
50
54
 
51
55
  ## Learn More
52
56
 
package/index.js CHANGED
@@ -131,6 +131,16 @@ function updatePackageJson(projectPath, projectName) {
131
131
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
132
132
  }
133
133
 
134
+ function updateTemplatePlaceholders(projectPath, projectName) {
135
+ const trustedTypesPolicyName = `${projectName}-html`;
136
+ const mainPath = path.join(projectPath, 'src', 'main.ts');
137
+ const source = fs.readFileSync(mainPath, 'utf8');
138
+ fs.writeFileSync(
139
+ mainPath,
140
+ source.replaceAll('__DALILA_TRUSTED_TYPES_POLICY__', trustedTypesPolicyName)
141
+ );
142
+ }
143
+
134
144
  function main() {
135
145
  ensureSupportedNode();
136
146
 
@@ -184,6 +194,7 @@ function main() {
184
194
 
185
195
  // Update package.json with project name
186
196
  updatePackageJson(projectPath, projectName);
197
+ updateTemplatePlaceholders(projectPath, projectName);
187
198
 
188
199
  // Success message
189
200
  console.log(`${green('Done!')} Created ${bold(projectName)}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-dalila",
3
- "version": "1.2.14",
3
+ "version": "1.2.15",
4
4
  "description": "Create Dalila apps with one command",
5
5
  "bin": {
6
6
  "create-dalila": "index.js"
@@ -0,0 +1,1040 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { createRequire } from 'node:module';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const FOUC_PREVENTION_STYLE = ` <style>[d-loading]{visibility:hidden}</style>`;
9
+ const SCRIPT_SOURCE_EXTENSIONS = new Set(['.ts', '.mts', '.cts']);
10
+ const SCRIPT_REQUEST_SOURCE_EXTENSIONS = new Set(['.js', '.mjs', '.cjs']);
11
+ const STATIC_DIR_EXCLUDES = new Set([
12
+ 'src',
13
+ 'public',
14
+ 'node_modules',
15
+ 'dist',
16
+ 'coverage',
17
+ 'playwright-report',
18
+ 'test-results',
19
+ ]);
20
+ const HTML_ENTRY_DIR_EXCLUDES = new Set([
21
+ 'node_modules',
22
+ 'dist',
23
+ 'coverage',
24
+ 'playwright-report',
25
+ 'test-results',
26
+ ]);
27
+ const STATIC_FILE_EXCLUDES = new Set([
28
+ 'package.json',
29
+ 'package-lock.json',
30
+ 'bun.lock',
31
+ 'bun.lockb',
32
+ 'pnpm-lock.yaml',
33
+ 'yarn.lock',
34
+ 'tsconfig.json',
35
+ 'build.mjs',
36
+ 'dev.mjs',
37
+ ]);
38
+
39
+ function resolvePackageModule(moduleName, projectDir) {
40
+ try {
41
+ return require.resolve(moduleName, { paths: [projectDir] });
42
+ } catch {
43
+ return require.resolve(moduleName);
44
+ }
45
+ }
46
+
47
+ function isScriptSourceFile(filePath) {
48
+ return SCRIPT_SOURCE_EXTENSIONS.has(path.extname(filePath)) && !filePath.endsWith('.d.ts');
49
+ }
50
+
51
+ function walkFiles(dir, files = []) {
52
+ if (!fs.existsSync(dir)) return files;
53
+
54
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
55
+ for (const entry of entries) {
56
+ const entryPath = path.join(dir, entry.name);
57
+ if (entry.isDirectory()) {
58
+ walkFiles(entryPath, files);
59
+ continue;
60
+ }
61
+
62
+ files.push(entryPath);
63
+ }
64
+
65
+ return files;
66
+ }
67
+
68
+ export function detectPreloadScripts(baseDir) {
69
+ const preloads = [];
70
+
71
+ for (const filePath of walkFiles(baseDir)) {
72
+ if (!isScriptSourceFile(filePath)) continue;
73
+
74
+ try {
75
+ const source = fs.readFileSync(filePath, 'utf8');
76
+ const persistRegex = /persist\s*\(\s*signal\s*(?:<[^>]+>)?\s*\(\s*['"]?([^'")]+)['"]?\s*\)\s*,\s*\{([^}]+)\}\s*\)/g;
77
+
78
+ let match;
79
+ while ((match = persistRegex.exec(source)) !== null) {
80
+ const defaultValue = match[1];
81
+ const options = match[2];
82
+
83
+ if (!options.includes('preload') || !options.match(/preload\s*:\s*true/)) {
84
+ continue;
85
+ }
86
+
87
+ const nameMatch = options.match(/name\s*:\s*['"]([^'"]+)['"]/);
88
+ if (!nameMatch) continue;
89
+
90
+ let storageType = 'localStorage';
91
+ const storageMatch = options.match(/storage\s*:\s*(\w+)/);
92
+ if (storageMatch && storageMatch[1] === 'sessionStorage') {
93
+ storageType = 'sessionStorage';
94
+ }
95
+
96
+ preloads.push({
97
+ name: nameMatch[1],
98
+ defaultValue,
99
+ storageType,
100
+ });
101
+ }
102
+ } catch {
103
+ // Ignore malformed files during packaging.
104
+ }
105
+ }
106
+
107
+ return preloads;
108
+ }
109
+
110
+ export function generatePreloadScript(name, defaultValue, storageType = 'localStorage') {
111
+ const k = JSON.stringify(name);
112
+ const d = JSON.stringify(defaultValue);
113
+ const script = `(function(){try{var v=${storageType}.getItem(${k});document.documentElement.setAttribute('data-theme',v?JSON.parse(v):${d})}catch(e){document.documentElement.setAttribute('data-theme',${d})}})();`;
114
+
115
+ return script
116
+ .replace(/</g, '\\x3C')
117
+ .replace(/-->/g, '--\\x3E')
118
+ .replace(/\u2028/g, '\\u2028')
119
+ .replace(/\u2029/g, '\\u2029');
120
+ }
121
+
122
+ function renderPreloadScriptTags(baseDir) {
123
+ const preloads = detectPreloadScripts(baseDir);
124
+ if (preloads.length === 0) return '';
125
+
126
+ return preloads
127
+ .map((preload) => ` <script>${generatePreloadScript(preload.name, preload.defaultValue, preload.storageType)}</script>`)
128
+ .join('\n');
129
+ }
130
+
131
+ function ensureFileExists(filePath, label) {
132
+ if (!fs.existsSync(filePath)) {
133
+ throw new Error(`Missing ${label}: ${filePath}`);
134
+ }
135
+ }
136
+
137
+ function copyDirectoryContents(sourceDir, destinationDir) {
138
+ if (!fs.existsSync(sourceDir)) return;
139
+ fs.mkdirSync(path.dirname(destinationDir), { recursive: true });
140
+ fs.cpSync(sourceDir, destinationDir, { recursive: true, force: true });
141
+ }
142
+
143
+ function copyStaticSourceAssets(sourceDir, destinationDir) {
144
+ if (!fs.existsSync(sourceDir)) return;
145
+
146
+ for (const filePath of walkFiles(sourceDir)) {
147
+ if (isScriptSourceFile(filePath) || filePath.endsWith('.d.ts')) {
148
+ continue;
149
+ }
150
+
151
+ const relativePath = path.relative(sourceDir, filePath);
152
+ const targetPath = path.join(destinationDir, relativePath);
153
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
154
+ fs.copyFileSync(filePath, targetPath);
155
+ }
156
+ }
157
+
158
+ function resolveDalilaPackageRoot(projectDir) {
159
+ const dalilaEntry = require.resolve('dalila', { paths: [projectDir] });
160
+ return path.dirname(path.dirname(dalilaEntry));
161
+ }
162
+
163
+ function formatTypeScriptError(ts, diagnostic) {
164
+ return ts.formatDiagnostic(diagnostic, {
165
+ getCanonicalFileName: (value) => value,
166
+ getCurrentDirectory: () => process.cwd(),
167
+ getNewLine: () => '\n',
168
+ });
169
+ }
170
+
171
+ function parseTypeScriptConfig(ts, configPath) {
172
+ let unrecoverableDiagnostic = null;
173
+ const parsed = ts.getParsedCommandLineOfConfigFile(
174
+ configPath,
175
+ {},
176
+ {
177
+ ...ts.sys,
178
+ onUnRecoverableConfigFileDiagnostic(diagnostic) {
179
+ unrecoverableDiagnostic = diagnostic;
180
+ },
181
+ }
182
+ );
183
+
184
+ if (unrecoverableDiagnostic) {
185
+ throw new Error(formatTypeScriptError(ts, unrecoverableDiagnostic));
186
+ }
187
+
188
+ if (!parsed) {
189
+ throw new Error(`Failed to parse TypeScript config: ${configPath}`);
190
+ }
191
+
192
+ if (parsed.errors?.length) {
193
+ throw new Error(parsed.errors.map((diagnostic) => formatTypeScriptError(ts, diagnostic)).join('\n'));
194
+ }
195
+
196
+ return parsed;
197
+ }
198
+
199
+ function inferCommonSourceDir(fileNames, fallbackDir) {
200
+ const sourceDirs = fileNames
201
+ .filter((filePath) => !filePath.endsWith('.d.ts'))
202
+ .map((filePath) => path.dirname(path.resolve(filePath)));
203
+
204
+ if (sourceDirs.length === 0) {
205
+ return fallbackDir;
206
+ }
207
+
208
+ let commonDir = sourceDirs[0];
209
+ for (const nextDir of sourceDirs.slice(1)) {
210
+ while (commonDir !== path.dirname(commonDir)) {
211
+ const relativePath = path.relative(commonDir, nextDir);
212
+ if (relativePath === '' || isRelativePathInsideBase(relativePath)) {
213
+ break;
214
+ }
215
+ commonDir = path.dirname(commonDir);
216
+ }
217
+
218
+ const relativePath = path.relative(commonDir, nextDir);
219
+ if (relativePath !== '' && !isRelativePathInsideBase(relativePath)) {
220
+ return fallbackDir;
221
+ }
222
+ }
223
+
224
+ return commonDir;
225
+ }
226
+
227
+ function loadTypeScriptBuildConfig(projectDir) {
228
+ const ts = require(resolvePackageModule('typescript', projectDir));
229
+ const configPath = ts.findConfigFile(projectDir, ts.sys.fileExists, 'tsconfig.json');
230
+ const packageOutDirAbs = path.join(projectDir, 'dist');
231
+ const defaultSourceDirAbs = fs.existsSync(path.join(projectDir, 'src'))
232
+ ? path.join(projectDir, 'src')
233
+ : projectDir;
234
+
235
+ if (!configPath) {
236
+ return {
237
+ projectDir,
238
+ configPath: null,
239
+ configDir: projectDir,
240
+ compileOutDirAbs: packageOutDirAbs,
241
+ packageOutDirAbs,
242
+ rootDirAbs: projectDir,
243
+ sourceDirAbs: defaultSourceDirAbs,
244
+ };
245
+ }
246
+
247
+ const configDir = path.dirname(configPath);
248
+ const parsed = parseTypeScriptConfig(ts, configPath);
249
+ const inferredRootDirAbs = inferCommonSourceDir(parsed.fileNames, projectDir);
250
+ const explicitRootDirAbs = typeof parsed.options.rootDir === 'string'
251
+ ? path.resolve(configDir, parsed.options.rootDir)
252
+ : null;
253
+ const sourceDirAbs = explicitRootDirAbs
254
+ ?? (defaultSourceDirAbs !== projectDir ? defaultSourceDirAbs : inferredRootDirAbs);
255
+
256
+ return {
257
+ projectDir,
258
+ configPath,
259
+ configDir,
260
+ compileOutDirAbs: parsed.options.outDir
261
+ ? path.resolve(configDir, parsed.options.outDir)
262
+ : packageOutDirAbs,
263
+ packageOutDirAbs,
264
+ rootDirAbs: explicitRootDirAbs ?? inferredRootDirAbs,
265
+ sourceDirAbs,
266
+ };
267
+ }
268
+
269
+ function resolveExportTarget(target) {
270
+ if (typeof target === 'string') return target;
271
+ if (!target || typeof target !== 'object') return null;
272
+ return target.default || target.import || null;
273
+ }
274
+
275
+ function buildDalilaImportEntries(projectDir) {
276
+ const dalilaRoot = resolveDalilaPackageRoot(projectDir);
277
+ const dalilaPackageJson = JSON.parse(
278
+ fs.readFileSync(path.join(dalilaRoot, 'package.json'), 'utf8')
279
+ );
280
+ const distRoot = path.join(dalilaRoot, 'dist');
281
+ const distIndexPath = path.join(distRoot, 'index.js');
282
+ const imports = {};
283
+
284
+ for (const [subpath, target] of Object.entries(dalilaPackageJson.exports ?? {})) {
285
+ const exportTarget = resolveExportTarget(target);
286
+ if (!exportTarget || !exportTarget.endsWith('.js')) continue;
287
+
288
+ const absoluteTarget = path.resolve(dalilaRoot, exportTarget);
289
+ if (absoluteTarget !== distIndexPath && !absoluteTarget.startsWith(distRoot + path.sep)) {
290
+ continue;
291
+ }
292
+
293
+ const relativeTarget = path.relative(distRoot, absoluteTarget).replace(/\\/g, '/');
294
+ const specifier = subpath === '.' ? 'dalila' : `dalila/${subpath.slice(2)}`;
295
+ imports[specifier] = `/vendor/dalila/${relativeTarget}`;
296
+ }
297
+
298
+ return imports;
299
+ }
300
+
301
+ function normalizeNodeModulesImportTarget(target, projectDir, baseDirAbs = projectDir) {
302
+ if (typeof target !== 'string') return null;
303
+
304
+ const { pathname, suffix } = splitUrlTarget(target);
305
+ if (isUrlWithScheme(pathname)) {
306
+ return null;
307
+ }
308
+
309
+ if (pathname.startsWith('/node_modules/')) {
310
+ return {
311
+ relativeTarget: pathname.slice('/node_modules/'.length),
312
+ suffix,
313
+ };
314
+ }
315
+
316
+ if (pathname.startsWith('node_modules/')) {
317
+ return {
318
+ relativeTarget: pathname.slice('node_modules/'.length),
319
+ suffix,
320
+ };
321
+ }
322
+
323
+ if (!pathname.startsWith('./') && !pathname.startsWith('../')) {
324
+ return null;
325
+ }
326
+
327
+ const nodeModulesRoot = path.join(projectDir, 'node_modules');
328
+ const resolvedAbsPath = resolveLocalProjectPath(projectDir, pathname, baseDirAbs);
329
+ const relativeTarget = path.relative(nodeModulesRoot, resolvedAbsPath);
330
+ if (!isRelativePathInsideBase(relativeTarget)) {
331
+ return null;
332
+ }
333
+
334
+ return {
335
+ relativeTarget: toPosixPath(relativeTarget),
336
+ suffix,
337
+ };
338
+ }
339
+
340
+ function splitUrlTarget(target) {
341
+ const match = String(target).match(/^([^?#]*)([?#].*)?$/);
342
+ return {
343
+ pathname: match?.[1] ?? String(target),
344
+ suffix: match?.[2] ?? '',
345
+ };
346
+ }
347
+
348
+ function isUrlWithScheme(pathname) {
349
+ return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(pathname) || pathname.startsWith('//');
350
+ }
351
+
352
+ function isLocalUrlPath(pathname) {
353
+ return pathname.startsWith('/') || pathname.startsWith('./') || pathname.startsWith('../');
354
+ }
355
+
356
+ function resolveLocalProjectPath(projectDir, pathname, baseDirAbs = projectDir) {
357
+ if (pathname.startsWith('/')) {
358
+ return path.resolve(projectDir, pathname.slice(1));
359
+ }
360
+
361
+ return path.resolve(baseDirAbs, pathname);
362
+ }
363
+
364
+ function emittedExtensionCandidates(sourceExt) {
365
+ switch (sourceExt) {
366
+ case '.mts':
367
+ return ['.mjs', '.js'];
368
+ case '.cts':
369
+ return ['.cjs', '.js'];
370
+ case '.ts':
371
+ return ['.js'];
372
+ default:
373
+ return [];
374
+ }
375
+ }
376
+
377
+ function replaceExtension(filePath, nextExt) {
378
+ return `${filePath.slice(0, -path.extname(filePath).length)}${nextExt}`;
379
+ }
380
+
381
+ function toPosixPath(value) {
382
+ return value.replace(/\\/g, '/');
383
+ }
384
+
385
+ function isRelativePathInsideBase(relativePath) {
386
+ return !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
387
+ }
388
+
389
+ function getProjectRelativeCandidates(targetAbsPath, buildConfig) {
390
+ const relativeCandidates = [];
391
+ const relativeToRootDir = path.relative(buildConfig.rootDirAbs, targetAbsPath);
392
+ if (isRelativePathInsideBase(relativeToRootDir)) {
393
+ relativeCandidates.push(relativeToRootDir);
394
+ }
395
+
396
+ const relativeToProject = path.relative(buildConfig.projectDir, targetAbsPath);
397
+ if (isRelativePathInsideBase(relativeToProject)) {
398
+ relativeCandidates.push(relativeToProject);
399
+ }
400
+
401
+ return [...new Set(relativeCandidates)];
402
+ }
403
+
404
+ function resolveCompiledSourceOutputPath(sourceAbsPath, buildConfig) {
405
+ const sourceExt = path.extname(sourceAbsPath);
406
+ const candidateExts = emittedExtensionCandidates(sourceExt);
407
+ if (candidateExts.length === 0) return null;
408
+
409
+ for (const relativeCandidate of getProjectRelativeCandidates(sourceAbsPath, buildConfig)) {
410
+ for (const candidateExt of candidateExts) {
411
+ const compiledAbsPath = path.join(
412
+ buildConfig.compileOutDirAbs,
413
+ replaceExtension(relativeCandidate, candidateExt)
414
+ );
415
+ if (fs.existsSync(compiledAbsPath)) {
416
+ return compiledAbsPath;
417
+ }
418
+ }
419
+ }
420
+
421
+ return null;
422
+ }
423
+
424
+ function toPackagedUrlFromCompiledOutput(compiledAbsPath, buildConfig) {
425
+ return `/${toPosixPath(path.relative(buildConfig.compileOutDirAbs, compiledAbsPath))}`;
426
+ }
427
+
428
+ function resolvePackagedProjectUrlPath(targetAbsPath, buildConfig) {
429
+ const [relativeCandidate] = getProjectRelativeCandidates(targetAbsPath, buildConfig);
430
+ if (relativeCandidate == null) {
431
+ return null;
432
+ }
433
+
434
+ if (!relativeCandidate) {
435
+ return '/';
436
+ }
437
+
438
+ return `/${toPosixPath(relativeCandidate)}`;
439
+ }
440
+
441
+ function ensureTrailingSlash(urlPath) {
442
+ return urlPath.endsWith('/') ? urlPath : `${urlPath}/`;
443
+ }
444
+
445
+ function buildUserProjectImportEntries(buildConfig) {
446
+ const sourceDirUrl = resolvePackagedProjectUrlPath(buildConfig.sourceDirAbs, buildConfig) ?? '/src/';
447
+ return {
448
+ '@/': ensureTrailingSlash(sourceDirUrl),
449
+ };
450
+ }
451
+
452
+ function getScriptSourceRequestCandidates(requestAbsPath) {
453
+ const requestExt = path.extname(requestAbsPath);
454
+ if (SCRIPT_SOURCE_EXTENSIONS.has(requestExt)) {
455
+ return {
456
+ candidates: [requestAbsPath],
457
+ requireExistingFile: false,
458
+ };
459
+ }
460
+
461
+ if (!SCRIPT_REQUEST_SOURCE_EXTENSIONS.has(requestExt)) {
462
+ return {
463
+ candidates: [],
464
+ requireExistingFile: true,
465
+ };
466
+ }
467
+
468
+ switch (requestExt) {
469
+ case '.mjs':
470
+ return {
471
+ candidates: [replaceExtension(requestAbsPath, '.mts'), replaceExtension(requestAbsPath, '.ts')],
472
+ requireExistingFile: true,
473
+ };
474
+ case '.cjs':
475
+ return {
476
+ candidates: [replaceExtension(requestAbsPath, '.cts'), replaceExtension(requestAbsPath, '.ts')],
477
+ requireExistingFile: true,
478
+ };
479
+ case '.js':
480
+ default:
481
+ return {
482
+ candidates: [
483
+ replaceExtension(requestAbsPath, '.ts'),
484
+ replaceExtension(requestAbsPath, '.mts'),
485
+ replaceExtension(requestAbsPath, '.cts'),
486
+ ],
487
+ requireExistingFile: true,
488
+ };
489
+ }
490
+ }
491
+
492
+ function resolveLocalSourceScriptPath(target, buildConfig, baseDirAbs = buildConfig.projectDir) {
493
+ const { pathname } = splitUrlTarget(target);
494
+ if (!isLocalUrlPath(pathname) || isUrlWithScheme(pathname)) {
495
+ return null;
496
+ }
497
+
498
+ const requestAbsPath = resolveLocalProjectPath(buildConfig.projectDir, pathname, baseDirAbs);
499
+ const { candidates, requireExistingFile } = getScriptSourceRequestCandidates(requestAbsPath);
500
+ for (const candidateAbsPath of candidates) {
501
+ if (!requireExistingFile || fs.existsSync(candidateAbsPath)) {
502
+ return candidateAbsPath;
503
+ }
504
+ }
505
+
506
+ return null;
507
+ }
508
+
509
+ function rewriteLocalSourceTarget(target, buildConfig, baseDirAbs = buildConfig.projectDir) {
510
+ const { suffix } = splitUrlTarget(target);
511
+ const sourceAbsPath = resolveLocalSourceScriptPath(target, buildConfig, baseDirAbs);
512
+ if (!sourceAbsPath) {
513
+ return null;
514
+ }
515
+
516
+ const compiledAbsPath = resolveCompiledSourceOutputPath(sourceAbsPath, buildConfig);
517
+ if (!compiledAbsPath) {
518
+ throw new Error(`Compiled output not found for source target "${target}"`);
519
+ }
520
+
521
+ return `${toPackagedUrlFromCompiledOutput(compiledAbsPath, buildConfig)}${suffix}`;
522
+ }
523
+
524
+ function rewriteImportMapScopeName(scopeName, buildConfig, baseDirAbs = buildConfig.projectDir) {
525
+ const rewrittenSourceTarget = rewriteLocalSourceTarget(scopeName, buildConfig, baseDirAbs);
526
+ if (rewrittenSourceTarget) {
527
+ return rewrittenSourceTarget;
528
+ }
529
+
530
+ const { pathname, suffix } = splitUrlTarget(scopeName);
531
+ if (!isLocalUrlPath(pathname) || isUrlWithScheme(pathname)) {
532
+ return scopeName;
533
+ }
534
+
535
+ const packagedUrlPath = resolvePackagedProjectUrlPath(
536
+ resolveLocalProjectPath(buildConfig.projectDir, pathname, baseDirAbs),
537
+ buildConfig
538
+ );
539
+ if (!packagedUrlPath) {
540
+ return scopeName;
541
+ }
542
+
543
+ const needsTrailingSlash = pathname.endsWith('/') && packagedUrlPath !== '/' && !packagedUrlPath.endsWith('/');
544
+ return `${packagedUrlPath}${needsTrailingSlash ? '/' : ''}${suffix}`;
545
+ }
546
+
547
+ function getPackagePathParts(relativeTarget) {
548
+ const segments = relativeTarget.split('/').filter(Boolean);
549
+ if (segments.length === 0) {
550
+ throw new Error(`Invalid node_modules import target: "${relativeTarget}"`);
551
+ }
552
+
553
+ if (segments[0].startsWith('@')) {
554
+ if (segments.length < 2) {
555
+ throw new Error(`Invalid scoped package import target: "${relativeTarget}"`);
556
+ }
557
+ return segments.slice(0, 2);
558
+ }
559
+
560
+ return segments.slice(0, 1);
561
+ }
562
+
563
+ function rewriteImportMapTarget(projectDir, vendorDir, target, buildConfig, copiedPackages, baseDirAbs = projectDir) {
564
+ const normalizedTarget = normalizeNodeModulesImportTarget(target, projectDir, baseDirAbs);
565
+ if (normalizedTarget) {
566
+ const relativeTarget = normalizedTarget.relativeTarget;
567
+ const packagePathParts = getPackagePathParts(relativeTarget);
568
+ const packagePath = path.join(...packagePathParts);
569
+ const sourcePackageDir = path.join(projectDir, 'node_modules', packagePath);
570
+ const destinationPackageDir = path.join(vendorDir, 'node_modules', packagePath);
571
+ ensureFileExists(sourcePackageDir, `import-map package for target "${target}"`);
572
+
573
+ if (!copiedPackages.has(packagePath)) {
574
+ copyDirectoryContents(sourcePackageDir, destinationPackageDir);
575
+ copiedPackages.add(packagePath);
576
+ }
577
+
578
+ return `/vendor/node_modules/${relativeTarget}${normalizedTarget.suffix}`;
579
+ }
580
+
581
+ const rewrittenSourceTarget = rewriteLocalSourceTarget(target, buildConfig, baseDirAbs);
582
+ if (rewrittenSourceTarget) {
583
+ return rewrittenSourceTarget;
584
+ }
585
+
586
+ return target;
587
+ }
588
+
589
+ function packageExistingImportMapImports(projectDir, vendorDir, imports, buildConfig, copiedPackages = new Set(), baseDirAbs = projectDir) {
590
+ const rewrittenImports = {};
591
+
592
+ for (const [specifier, target] of Object.entries(imports ?? {})) {
593
+ rewrittenImports[specifier] = rewriteImportMapTarget(projectDir, vendorDir, target, buildConfig, copiedPackages, baseDirAbs);
594
+ }
595
+
596
+ return rewrittenImports;
597
+ }
598
+
599
+ function packageExistingImportMapScopes(projectDir, vendorDir, scopes, buildConfig, copiedPackages = new Set(), baseDirAbs = projectDir) {
600
+ const rewrittenScopes = {};
601
+
602
+ for (const [scopeName, scopeImports] of Object.entries(scopes ?? {})) {
603
+ const rewrittenScopeName = rewriteImportMapScopeName(scopeName, buildConfig, baseDirAbs);
604
+ if (!scopeImports || typeof scopeImports !== 'object' || Array.isArray(scopeImports)) {
605
+ rewrittenScopes[rewrittenScopeName] = scopeImports;
606
+ continue;
607
+ }
608
+
609
+ const rewrittenScopeImports = packageExistingImportMapImports(
610
+ projectDir,
611
+ vendorDir,
612
+ scopeImports,
613
+ buildConfig,
614
+ copiedPackages,
615
+ baseDirAbs
616
+ );
617
+
618
+ rewrittenScopes[rewrittenScopeName] = {
619
+ ...(rewrittenScopes[rewrittenScopeName] ?? {}),
620
+ ...rewrittenScopeImports,
621
+ };
622
+ }
623
+
624
+ return rewrittenScopes;
625
+ }
626
+
627
+ function extractImportMap(html) {
628
+ const importMapPattern = /<script[^>]*type=["']importmap["'][^>]*>([\s\S]*?)<\/script>/i;
629
+ const match = html.match(importMapPattern);
630
+ if (!match) {
631
+ return {
632
+ html,
633
+ importMap: { imports: {} },
634
+ };
635
+ }
636
+
637
+ let importMap = { imports: {} };
638
+ try {
639
+ const parsed = JSON.parse(match[1]);
640
+ if (parsed && typeof parsed === 'object') {
641
+ importMap = parsed;
642
+ }
643
+ } catch {
644
+ // Ignore invalid import maps and replace them with the packaged version.
645
+ }
646
+
647
+ return {
648
+ html: html.replace(match[0], '').trimEnd() + '\n',
649
+ importMap,
650
+ };
651
+ }
652
+
653
+ function renderImportMapScript(importMap) {
654
+ const payload = JSON.stringify(importMap, null, 2)
655
+ .split('\n')
656
+ .map(line => ` ${line}`)
657
+ .join('\n');
658
+
659
+ return ` <script type="importmap">\n${payload}\n </script>`;
660
+ }
661
+
662
+ function shouldPackageHtmlEntry(source) {
663
+ return /<script[^>]*type=["']module["'][^>]*>/i.test(source)
664
+ || /<script[^>]*type=["']importmap["'][^>]*>/i.test(source);
665
+ }
666
+
667
+ function injectHeadContent(html, fragments) {
668
+ const content = fragments.filter(Boolean).join('\n');
669
+ if (!content) return html;
670
+
671
+ const headOpenMatch = html.match(/<head\b[^>]*>/i);
672
+ const headCloseMatch = html.match(/<\/head>/i);
673
+ if (!headOpenMatch || headOpenMatch.index == null || !headCloseMatch || headCloseMatch.index == null) {
674
+ return `${content}\n${html}`;
675
+ }
676
+
677
+ const headStart = headOpenMatch.index + headOpenMatch[0].length;
678
+ const headEnd = headCloseMatch.index;
679
+ const headContent = html.slice(headStart, headEnd);
680
+ const moduleScriptMatch = headContent.match(/<script\b[^>]*\btype=["']module["'][^>]*>/i);
681
+ const stylesheetMatch = headContent.match(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/i);
682
+ const insertionOffset = moduleScriptMatch?.index
683
+ ?? stylesheetMatch?.index
684
+ ?? headContent.length;
685
+ const insertionIndex = headStart + insertionOffset;
686
+
687
+ return `${html.slice(0, insertionIndex)}\n${content}\n${html.slice(insertionIndex)}`;
688
+ }
689
+
690
+ function rewriteInlineModuleSpecifiers(source, buildConfig, baseDirAbs = buildConfig.projectDir) {
691
+ const rewriteStaticSpecifierMatch = (fullMatch, prefix, quote, specifier) => {
692
+ const rewrittenSpecifier = rewriteLocalSourceTarget(specifier, buildConfig, baseDirAbs);
693
+ if (!rewrittenSpecifier) {
694
+ return fullMatch;
695
+ }
696
+
697
+ return `${prefix}${quote}${rewrittenSpecifier}${quote}`;
698
+ };
699
+
700
+ const rewriteDynamicSpecifierMatch = (fullMatch, prefix, quote, specifier, suffix) => {
701
+ const rewrittenSpecifier = rewriteLocalSourceTarget(specifier, buildConfig, baseDirAbs);
702
+ if (!rewrittenSpecifier) {
703
+ return fullMatch;
704
+ }
705
+
706
+ return `${prefix}${quote}${rewrittenSpecifier}${quote}${suffix}`;
707
+ };
708
+
709
+ let rewrittenSource = source.replace(
710
+ /(\bimport\s*\(\s*)(['"])([^'"]+)\2(\s*\))/g,
711
+ rewriteDynamicSpecifierMatch
712
+ );
713
+
714
+ rewrittenSource = rewrittenSource.replace(
715
+ /(\bfrom\s*)(['"])([^'"]+)\2/g,
716
+ rewriteStaticSpecifierMatch
717
+ );
718
+
719
+ rewrittenSource = rewrittenSource.replace(
720
+ /(\bimport\s+)(['"])([^'"]+)\2/g,
721
+ rewriteStaticSpecifierMatch
722
+ );
723
+
724
+ return rewrittenSource;
725
+ }
726
+
727
+ function rewritePackagedModuleSpecifiers(buildConfig) {
728
+ for (const filePath of walkFiles(buildConfig.packageOutDirAbs)) {
729
+ if (!SCRIPT_REQUEST_SOURCE_EXTENSIONS.has(path.extname(filePath))) {
730
+ continue;
731
+ }
732
+
733
+ const source = fs.readFileSync(filePath, 'utf8');
734
+ const rewrittenSource = rewriteInlineModuleSpecifiers(source, buildConfig);
735
+ if (rewrittenSource !== source) {
736
+ fs.writeFileSync(filePath, rewrittenSource);
737
+ }
738
+ }
739
+ }
740
+
741
+ function rewriteHtmlModuleScripts(html, buildConfig, baseDirAbs = buildConfig.projectDir) {
742
+ return html.replace(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi, (fullMatch, attrs, content) => {
743
+ const typeMatch = attrs.match(/\btype=["']([^"']+)["']/i);
744
+ if (!typeMatch || typeMatch[1] !== 'module') {
745
+ return fullMatch;
746
+ }
747
+
748
+ const srcMatch = attrs.match(/\bsrc=["']([^"']+)["']/i);
749
+ if (srcMatch) {
750
+ const rewrittenSrc = rewriteLocalSourceTarget(srcMatch[1], buildConfig, baseDirAbs);
751
+ if (!rewrittenSrc) {
752
+ return fullMatch;
753
+ }
754
+
755
+ return fullMatch.replace(srcMatch[0], `src="${rewrittenSrc}"`);
756
+ }
757
+
758
+ const rewrittenContent = rewriteInlineModuleSpecifiers(content, buildConfig, baseDirAbs);
759
+ if (rewrittenContent === content) {
760
+ return fullMatch;
761
+ }
762
+
763
+ return `<script${attrs}>${rewrittenContent}</script>`;
764
+ });
765
+ }
766
+
767
+ function buildHtmlDocument(sourceHtmlPath, importEntries, buildConfig) {
768
+ const source = fs.readFileSync(sourceHtmlPath, 'utf8');
769
+ const { html: htmlWithoutImportMap } = extractImportMap(source);
770
+ const html = rewriteHtmlModuleScripts(htmlWithoutImportMap, buildConfig, path.dirname(sourceHtmlPath));
771
+
772
+ return injectHeadContent(html, [
773
+ FOUC_PREVENTION_STYLE,
774
+ renderPreloadScriptTags(buildConfig.sourceDirAbs),
775
+ renderImportMapScript(importEntries),
776
+ ]);
777
+ }
778
+
779
+ function pathsOverlap(leftPath, rightPath) {
780
+ const normalizedLeft = path.resolve(leftPath);
781
+ const normalizedRight = path.resolve(rightPath);
782
+
783
+ return normalizedLeft === normalizedRight
784
+ || normalizedLeft.startsWith(`${normalizedRight}${path.sep}`)
785
+ || normalizedRight.startsWith(`${normalizedLeft}${path.sep}`);
786
+ }
787
+
788
+ function copyCompiledOutputTree(buildConfig) {
789
+ if (buildConfig.compileOutDirAbs === buildConfig.packageOutDirAbs) {
790
+ ensureFileExists(buildConfig.packageOutDirAbs, 'compiled output directory');
791
+ return {
792
+ buildConfig,
793
+ cleanup() {},
794
+ };
795
+ }
796
+
797
+ ensureFileExists(buildConfig.compileOutDirAbs, 'compiled output directory');
798
+ let snapshotRoot = null;
799
+ let compileOutDirAbs = buildConfig.compileOutDirAbs;
800
+
801
+ try {
802
+ if (pathsOverlap(buildConfig.compileOutDirAbs, buildConfig.packageOutDirAbs)) {
803
+ snapshotRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'dalila-build-'));
804
+ compileOutDirAbs = path.join(snapshotRoot, 'compiled');
805
+ copyDirectoryContents(buildConfig.compileOutDirAbs, compileOutDirAbs);
806
+ }
807
+
808
+ fs.rmSync(buildConfig.packageOutDirAbs, { recursive: true, force: true });
809
+ copyDirectoryContents(compileOutDirAbs, buildConfig.packageOutDirAbs);
810
+
811
+ return {
812
+ buildConfig: compileOutDirAbs === buildConfig.compileOutDirAbs
813
+ ? buildConfig
814
+ : { ...buildConfig, compileOutDirAbs },
815
+ cleanup() {
816
+ if (snapshotRoot) {
817
+ fs.rmSync(snapshotRoot, { recursive: true, force: true });
818
+ }
819
+ },
820
+ };
821
+ } catch (error) {
822
+ if (snapshotRoot) {
823
+ fs.rmSync(snapshotRoot, { recursive: true, force: true });
824
+ }
825
+ throw error;
826
+ }
827
+ }
828
+
829
+ function getTopLevelProjectDirName(projectDir, targetDirAbs) {
830
+ const relativePath = path.relative(projectDir, targetDirAbs);
831
+ if (!relativePath || relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
832
+ return null;
833
+ }
834
+
835
+ const [topLevelName] = relativePath.split(path.sep);
836
+ return topLevelName || null;
837
+ }
838
+
839
+ function collectTopLevelStaticDirs(projectDir, buildConfig) {
840
+ const excludedNames = new Set(STATIC_DIR_EXCLUDES);
841
+ const sourceTopLevelDir = getTopLevelProjectDirName(projectDir, buildConfig.rootDirAbs);
842
+ const compileTopLevelDir = getTopLevelProjectDirName(projectDir, buildConfig.compileOutDirAbs);
843
+ const packageTopLevelDir = getTopLevelProjectDirName(projectDir, buildConfig.packageOutDirAbs);
844
+
845
+ if (sourceTopLevelDir && buildConfig.rootDirAbs !== buildConfig.projectDir) {
846
+ excludedNames.add(sourceTopLevelDir);
847
+ }
848
+
849
+ if (compileTopLevelDir) {
850
+ excludedNames.add(compileTopLevelDir);
851
+ }
852
+
853
+ if (packageTopLevelDir) {
854
+ excludedNames.add(packageTopLevelDir);
855
+ }
856
+
857
+ return fs.readdirSync(projectDir, { withFileTypes: true })
858
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.') && !excludedNames.has(entry.name))
859
+ .map((entry) => entry.name);
860
+ }
861
+
862
+ function copyTopLevelStaticDirs(projectDir, packageOutDirAbs, buildConfig) {
863
+ for (const dirName of collectTopLevelStaticDirs(projectDir, buildConfig)) {
864
+ copyDirectoryContents(path.join(projectDir, dirName), path.join(packageOutDirAbs, dirName));
865
+ }
866
+ }
867
+
868
+ function copyTopLevelStaticFiles(projectDir, packageOutDirAbs) {
869
+ for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {
870
+ if (!entry.isFile() || entry.name.startsWith('.') || STATIC_FILE_EXCLUDES.has(entry.name)) {
871
+ continue;
872
+ }
873
+
874
+ const sourcePath = path.join(projectDir, entry.name);
875
+ const destinationPath = path.join(packageOutDirAbs, entry.name);
876
+ fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
877
+ fs.copyFileSync(sourcePath, destinationPath);
878
+ }
879
+ }
880
+
881
+ function resolveSourceAssetRoots(projectDir, buildConfig) {
882
+ const sourceRoots = [];
883
+ const srcDir = path.join(projectDir, 'src');
884
+
885
+ if (buildConfig.rootDirAbs !== projectDir && fs.existsSync(buildConfig.rootDirAbs)) {
886
+ sourceRoots.push(buildConfig.rootDirAbs);
887
+ }
888
+
889
+ if (fs.existsSync(srcDir)) {
890
+ sourceRoots.push(srcDir);
891
+ }
892
+
893
+ return [...new Set(sourceRoots)];
894
+ }
895
+
896
+ function copyPackagedSourceAssets(projectDir, buildConfig) {
897
+ for (const sourceDir of resolveSourceAssetRoots(projectDir, buildConfig)) {
898
+ for (const filePath of walkFiles(sourceDir)) {
899
+ if (isScriptSourceFile(filePath) || filePath.endsWith('.d.ts')) {
900
+ continue;
901
+ }
902
+
903
+ const destinationPaths = new Set();
904
+ const projectRelativePath = path.relative(projectDir, filePath);
905
+ if (isRelativePathInsideBase(projectRelativePath)) {
906
+ destinationPaths.add(path.join(buildConfig.packageOutDirAbs, projectRelativePath));
907
+ }
908
+
909
+ const packagedUrlPath = resolvePackagedProjectUrlPath(filePath, buildConfig);
910
+ if (packagedUrlPath && packagedUrlPath !== '/') {
911
+ destinationPaths.add(path.join(buildConfig.packageOutDirAbs, packagedUrlPath.slice(1)));
912
+ }
913
+
914
+ for (const destinationPath of destinationPaths) {
915
+ fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
916
+ fs.copyFileSync(filePath, destinationPath);
917
+ }
918
+ }
919
+ }
920
+ }
921
+
922
+ function walkProjectHtmlFiles(dir, files = []) {
923
+ if (!fs.existsSync(dir)) return files;
924
+
925
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
926
+ for (const entry of entries) {
927
+ if (entry.name.startsWith('.')) {
928
+ continue;
929
+ }
930
+
931
+ const entryPath = path.join(dir, entry.name);
932
+ if (entry.isDirectory()) {
933
+ if (HTML_ENTRY_DIR_EXCLUDES.has(entry.name)) {
934
+ continue;
935
+ }
936
+ walkProjectHtmlFiles(entryPath, files);
937
+ continue;
938
+ }
939
+
940
+ if (entry.isFile() && entry.name.endsWith('.html')) {
941
+ files.push(entryPath);
942
+ }
943
+ }
944
+
945
+ return files;
946
+ }
947
+
948
+ function packageHtmlEntryPoints(projectDir, vendorDir, buildConfig, copiedPackages) {
949
+ for (const sourceHtmlPath of walkProjectHtmlFiles(projectDir)) {
950
+ const source = fs.readFileSync(sourceHtmlPath, 'utf8');
951
+ if (!shouldPackageHtmlEntry(source)) {
952
+ continue;
953
+ }
954
+
955
+ const { importMap: existingImportMap } = extractImportMap(source);
956
+ const existingImports = existingImportMap && typeof existingImportMap.imports === 'object'
957
+ ? existingImportMap.imports
958
+ : {};
959
+ const existingScopes = existingImportMap && typeof existingImportMap.scopes === 'object'
960
+ ? existingImportMap.scopes
961
+ : {};
962
+ const baseDirAbs = path.dirname(sourceHtmlPath);
963
+ const rewrittenImports = packageExistingImportMapImports(
964
+ projectDir,
965
+ vendorDir,
966
+ existingImports,
967
+ buildConfig,
968
+ copiedPackages,
969
+ baseDirAbs
970
+ );
971
+ const rewrittenScopes = packageExistingImportMapScopes(
972
+ projectDir,
973
+ vendorDir,
974
+ existingScopes,
975
+ buildConfig,
976
+ copiedPackages,
977
+ baseDirAbs
978
+ );
979
+ const importMap = {
980
+ ...existingImportMap,
981
+ imports: {
982
+ ...rewrittenImports,
983
+ ...buildUserProjectImportEntries(buildConfig),
984
+ ...buildDalilaImportEntries(projectDir),
985
+ },
986
+ scopes: rewrittenScopes,
987
+ };
988
+ const packagedHtml = buildHtmlDocument(sourceHtmlPath, importMap, buildConfig);
989
+ const packagedHtmlPath = path.join(buildConfig.packageOutDirAbs, path.relative(projectDir, sourceHtmlPath));
990
+ fs.mkdirSync(path.dirname(packagedHtmlPath), { recursive: true });
991
+ fs.writeFileSync(packagedHtmlPath, packagedHtml);
992
+ }
993
+ }
994
+
995
+ export async function buildProject(projectDir = process.cwd()) {
996
+ const rootDir = path.resolve(projectDir);
997
+ const initialBuildConfig = loadTypeScriptBuildConfig(rootDir);
998
+ const distDir = initialBuildConfig.packageOutDirAbs;
999
+ const vendorDir = path.join(distDir, 'vendor');
1000
+ const dalilaRoot = resolveDalilaPackageRoot(rootDir);
1001
+ const indexHtmlPath = path.join(rootDir, 'index.html');
1002
+
1003
+ ensureFileExists(indexHtmlPath, 'index.html');
1004
+ const compiledOutput = copyCompiledOutputTree(initialBuildConfig);
1005
+ const buildConfig = compiledOutput.buildConfig;
1006
+
1007
+ try {
1008
+ fs.rmSync(vendorDir, { recursive: true, force: true });
1009
+ rewritePackagedModuleSpecifiers(buildConfig);
1010
+ copyDirectoryContents(path.join(dalilaRoot, 'dist'), path.join(vendorDir, 'dalila'));
1011
+ copyPackagedSourceAssets(rootDir, buildConfig);
1012
+ copyDirectoryContents(path.join(rootDir, 'public'), path.join(distDir, 'public'));
1013
+ copyTopLevelStaticDirs(rootDir, distDir, buildConfig);
1014
+ copyTopLevelStaticFiles(rootDir, distDir);
1015
+
1016
+ const copiedPackages = new Set();
1017
+ packageHtmlEntryPoints(rootDir, vendorDir, buildConfig, copiedPackages);
1018
+
1019
+ return {
1020
+ distDir,
1021
+ importEntries: {
1022
+ imports: {
1023
+ ...buildUserProjectImportEntries(buildConfig),
1024
+ ...buildDalilaImportEntries(rootDir),
1025
+ },
1026
+ },
1027
+ };
1028
+ } finally {
1029
+ compiledOutput.cleanup();
1030
+ }
1031
+ }
1032
+
1033
+ const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
1034
+
1035
+ if (isMain) {
1036
+ buildProject().catch((error) => {
1037
+ console.error('[Dalila] build packaging failed:', error);
1038
+ process.exit(1);
1039
+ });
1040
+ }
@@ -9,6 +9,13 @@
9
9
  <body>
10
10
  <div id="app" d-loading></div>
11
11
 
12
+ <script type="importmap">
13
+ {
14
+ "imports": {
15
+ "dompurify": "/node_modules/dompurify/dist/purify.es.mjs"
16
+ }
17
+ }
18
+ </script>
12
19
  <script type="module" src="/src/main.ts"></script>
13
20
  </body>
14
21
  </html>
@@ -7,7 +7,7 @@
7
7
  "dev": "node dev.mjs",
8
8
  "routes": "dalila routes generate",
9
9
  "routes:watch": "dalila routes watch",
10
- "build": "tsc",
10
+ "build": "dalila routes generate && tsc && node build.mjs",
11
11
  "preview": "dalila-dev --dist",
12
12
  "postinstall": "dalila routes generate"
13
13
  },
@@ -15,7 +15,8 @@
15
15
  "node": ">=22.6.0"
16
16
  },
17
17
  "dependencies": {
18
- "dalila": "^1.9.21"
18
+ "dalila": "^1.9.23",
19
+ "dompurify": "^3.2.7"
19
20
  },
20
21
  "devDependencies": {
21
22
  "typescript": "^5.7.3"
@@ -1,7 +1,24 @@
1
+ import DOMPurify from 'dompurify';
2
+ import { configure } from 'dalila/runtime';
1
3
  import { createRouter } from 'dalila/router';
2
4
  import { routes } from '../routes.generated.js';
3
5
  import { routeManifest } from '../routes.generated.manifest.js';
4
6
 
7
+ configure({
8
+ sanitizeHtml: (html) => DOMPurify.sanitize(html, {
9
+ USE_PROFILES: { html: true },
10
+ FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'style'],
11
+ FORBID_ATTR: ['srcdoc'],
12
+ ALLOW_UNKNOWN_PROTOCOLS: false,
13
+ RETURN_TRUSTED_TYPE: false,
14
+ }),
15
+ security: {
16
+ strict: true,
17
+ trustedTypes: true,
18
+ trustedTypesPolicyName: '__DALILA_TRUSTED_TYPES_POLICY__',
19
+ },
20
+ });
21
+
5
22
  const outlet = document.getElementById('app');
6
23
 
7
24
  if (!outlet) {