create-lupine 1.0.25 → 1.0.27

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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lupine",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "Scaffolding tool for Lupine.js projects",
5
5
  "bin": {
6
6
  "create-lupine": "index.js"
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "npm-publish": "npm publish --access public",
14
- "test": "node index.js"
14
+ "dev-test": "node index.js"
15
15
  },
16
16
  "dependencies": {},
17
17
  "type": "module"
@@ -26,6 +26,23 @@ When performing multi-line code refactoring or replacement operations (`replace_
26
26
  - ✅ **Use when**: The component is small, state drives most of the UI, and the React-style patterns feel natural.
27
27
  - ⚠️ **Avoid when**: The component is large/complex, or only a tiny portion of the UI needs to change (e.g. a progress counter, a list inside a page) — repeated full rerenders are wasteful.
28
28
  - **`ref.onLoad` + useState**: `onLoad` is called **only on initial mount** (not on rerenders). It's the right place for async data fetch that populates state.
29
+ - **Async component functions**: Component functions **can be `async`** — the framework detects the returned Promise and `await`s it. This lets you `await fetch()` directly inside a component body. However, **all `useState()` calls MUST appear before the first `await`**. The internal `_currentStore` pointer is cleared immediately after the synchronous portion of the component executes (before any `await`), so calling `useState()` after an `await` will throw `"useState must be called inside a component function"`.
30
+
31
+ ```tsx
32
+ // ✅ Correct: useState before await
33
+ const MyComp = async (props) => {
34
+ const [data, setData] = useState(null); // hooks first
35
+ const result = await fetchData(); // await after hooks
36
+ return <div>{result.title}</div>;
37
+ };
38
+
39
+ // ❌ Wrong: useState after await — will crash
40
+ const MyComp = async (props) => {
41
+ const result = await fetchData(); // await first
42
+ const [data, setData] = useState(null); // 💥 _currentStore is null
43
+ return <div>{result.title}</div>;
44
+ };
45
+ ```
29
46
 
30
47
  - **`HtmlVar` — Surgical partial updates (large/complex components)**:
31
48
 
@@ -199,7 +216,7 @@ When you pass `css={css}` to a JSX element, Lupine automatically evaluates it an
199
216
  4. Use `class="&-item"` normally. Lupine replaces `&` with the identical `globalCssId` across all instances.
200
217
 
201
218
  > [!WARNING]
202
- > Because `getGlobalStylesId` relies on `getRequestContext()` data to correctly attach and track styles (especially across SSR and interactive client renders), getGlobalStylesId and `bindGlobalStyle` **MUST** be called inside the component function scope. Calling them at the file/module level will result in runtime errors.
219
+ > Because `getGlobalStylesId` relies on `getRequestContext()` data to correctly attach and track styles (especially across SSR and interactive client renders), `getGlobalStylesId` and `bindGlobalStyle` **MUST** be called inside the component function scope. Calling them at the file/module level will result in runtime errors like: `Uncaught Error: Request context is not initialized`.
203
220
 
204
221
  ### ⚠️ IMPORTANT: The "Static `CssProps`" Rule
205
222
 
@@ -460,7 +477,231 @@ const Parent = () => {
460
477
  - Fixed parameters use `/fixed-parameter/` (e.g., `pageRouter.use('/page/:id/detail/', PlayPage)`), `detail` is a fixed parameter.
461
478
  - Optional parameters use `?` (e.g., `/page/:userId/?option1/?option2`). Once an optional parameter is declared, all subsequent route sections become optional (It's not a query string).
462
479
 
463
- ## 6. Coding Standards & Gotchas
480
+ ## 6. Lupine.cms App Integration Guide
481
+
482
+ This section describes Lupine.cms from the app user's point of view: how a frontend app routes public/SSR requests into CMS rendering, how CMS URLs are resolved, how language fallback works, how coded pages/components override CMS content, and where registration code must live.
483
+
484
+ ### 6.1 Required frontend entry point
485
+
486
+ A CMS-enabled frontend app must register `CmsRenderPage` as the catch-all page handler in its public app entry, for example in `apps/cms/web/src/index.tsx`:
487
+
488
+ ```tsx
489
+ pageRouter.use('*', CmsRenderPage);
490
+ ```
491
+
492
+ This is the key integration point. During frontend rendering or SSR, any page request that reaches this catch-all route is delegated to `CmsRenderPage`. Without this route, CMS page JSON will not be used for public page requests.
493
+
494
+ Typical public entry responsibilities:
495
+
496
+ - initialize app/global styles and theme
497
+ - register any public coded CMS frame/content pages with `cmsRegisterPage()`
498
+ - configure `pageRouter`
499
+ - end with `pageRouter.use('*', CmsRenderPage)` so unresolved public routes are served by CMS
500
+
501
+ ### 6.2 CMS URL structure: `/lang/frame/content`
502
+
503
+ CMS public URLs are interpreted as three logical levels:
504
+
505
+ ```text
506
+ /lang/frame/content
507
+ ```
508
+
509
+ Meanings:
510
+
511
+ - `lang`: optional language code
512
+ - `frame`: optional frame page ID
513
+ - `content`: optional content page ID
514
+
515
+ If the first URL segment matches a configured site language, it is treated as `lang`. Otherwise the URL is treated as not having a language prefix.
516
+
517
+ Examples:
518
+
519
+ ```text
520
+ /en/home/article-1 -> lang=en, frame=home, content=article-1
521
+ /home/article-1 -> lang is resolved automatically, frame=home, content=article-1
522
+ /en/home -> lang=en, frame=home, content defaults from config
523
+ /home -> lang auto, frame=home, content defaults from config
524
+ / -> lang auto, frame and content default from config
525
+ ```
526
+
527
+ ### 6.3 Language resolution and default language
528
+
529
+ If `lang` is omitted from the URL, CMS resolves language in this order:
530
+
531
+ 1. explicit `lang` parameter passed to the render/embed API, if any
532
+ 2. language in the URL prefix, if any
533
+ 3. browser language
534
+ 4. the default language configured in `siteLangs`
535
+
536
+ The available languages are configured in the web settings UI using the `siteLangs` setting declared in `packages/lupine.api/admin/admin-setting-web.tsx`:
537
+
538
+ ```typescript
539
+ { label: 'Languages', type: 'text', name: 'siteLangs', tip: 'Languages separated by commas. Format: code:title,code:title. First is default language. Example: en:English,cn:Chinese,ja:Japanese' }
540
+ ```
541
+
542
+ The first item in `siteLangs` is the default language. If the browser language is not in `siteLangs`, CMS falls back to this first language.
543
+
544
+ Agent rules:
545
+
546
+ - Do not assume browser language is always accepted. Always compare it against configured `siteLangs`.
547
+ - Keep the first `siteLangs` entry stable because it is the fallback default for public rendering and data fallback.
548
+ - When testing multilingual pages, test both explicit language URLs and language omitted URLs.
549
+
550
+ ### 6.4 Default frame and content IDs
551
+
552
+ When `frame` or `content` is missing from the URL, CMS uses web configuration defaults from `render-page.tsx`:
553
+
554
+ ```typescript
555
+ const frameId = (hasLangPrefix ? urlParts[1] : urlParts[0]) || await WebConfig.get('cmsPageDefault', '');
556
+ const contentId = (hasLangPrefix ? urlParts[2] : urlParts[1]) || await WebConfig.get('cmsContentDefault', '');
557
+ ```
558
+
559
+ Therefore:
560
+
561
+ - missing frame ID falls back to `cmsPageDefault`
562
+ - missing content ID falls back to `cmsContentDefault`
563
+
564
+ Agent rules:
565
+
566
+ - If `/` renders incorrectly, inspect `cmsPageDefault` and `cmsContentDefault` first.
567
+ - If `/en` renders incorrectly, remember it may still be using default frame/content IDs.
568
+ - Do not hardcode default page IDs in rendering logic; use `WebConfig` settings.
569
+
570
+ ### 6.5 CMS page data lookup and language fallback
571
+
572
+ When CMS loads persisted page JSON from the backend, it uses language-qualified IDs first:
573
+
574
+ 1. try `lang:id`
575
+ 2. if missing, try `defaultLang:id`
576
+ 3. if still missing, try plain `id`
577
+
578
+ This applies to frame/content page data loaded from CMS storage.
579
+
580
+ Practical result:
581
+
582
+ - Save `en:home` to create an English-specific home page.
583
+ - Save `cn:home` to create a Chinese-specific home page.
584
+ - Save `home` as a language-neutral fallback.
585
+ - If `ja:home` does not exist, CMS can fall back to default language and then plain `home`.
586
+
587
+ Agent rules:
588
+
589
+ - When investigating "wrong language page displayed", check all three possible records: `lang:id`, `defaultLang:id`, and `id`.
590
+ - When creating multilingual content, use `lang:id` consistently in the designer save dialog.
591
+ - Keep fallback pages plain or default-language records intentionally; do not create accidental stale fallbacks.
592
+
593
+ ### 6.6 Coded frame/content pages with `cmsRegisterPage()`
594
+
595
+ A frontend app can override or provide a frame/content page with code:
596
+
597
+ ```tsx
598
+ const SampleFrame = async (props: PageProps) => {
599
+ return <div>Frame from code</div>;
600
+ };
601
+
602
+ cmsRegisterPage('pagex', SampleFrame);
603
+ ```
604
+
605
+ When CMS resolves a frame/content ID, registered pages are checked before persisted CMS JSON. This means a coded registration for `pagex` takes priority over a database page with the same ID.
606
+
607
+ Use this when:
608
+
609
+ - a frame should be implemented in code for full control
610
+ - content is dynamic and not suited to page-designer JSON
611
+ - you want a coded fallback/override for a CMS ID
612
+
613
+ Agent rules:
614
+
615
+ - Registered page keys are public render contracts. Changing them can break URLs and saved CMS references.
616
+ - Keep `cmsRegisterPage()` calls in the public frontend entry when they are needed for public `CmsRenderPage` routing.
617
+ - If a CMS page ID is not loading from database, check whether the same ID was registered with `cmsRegisterPage()` and is taking priority.
618
+
619
+ ### 6.7 Saving pages and using `My Components`
620
+
621
+ In the design UI, pages can be saved with language-qualified IDs such as:
622
+
623
+ ```text
624
+ en:home
625
+ cn:home
626
+ home
627
+ ```
628
+
629
+ If the save dialog has `component` checked, the saved content is also treated as a reusable saved component. It becomes available under `My Components` in the designer and can be dragged into other pages.
630
+
631
+ Important distinction:
632
+
633
+ - A normal saved page/content is resolved by URL and ID.
634
+ - A checked component save is additionally discoverable as a reusable design component.
635
+ - Saved components are persisted CMS data, unlike registered code components which are runtime registrations.
636
+
637
+ Agent rules:
638
+
639
+ - Use saved components for reusable page-designer JSON fragments.
640
+ - Use registered code components for reusable coded JSX/TSX components.
641
+ - Do not confuse `My Components` with `Registered Components`.
642
+
643
+ ### 6.8 Coded designer components with `cmsRegisterComponent()`
644
+
645
+ The designer can also expose coded components as draggable design items:
646
+
647
+ ```tsx
648
+ const SampleContent = async (props: PageProps) => {
649
+ return (
650
+ <div class='sample-content-box'>
651
+ This is a sample registered components.
652
+ </div>
653
+ );
654
+ };
655
+
656
+ cmsRegisterComponent('contentx', SampleContent, 'Sample Content');
657
+ ```
658
+
659
+ These appear in the designer under `Registered Components`. Dragging one into a page stores a `block-registered-component` node with only a component key/label. At render time, CMS looks up the real function from the registered component map and renders it.
660
+
661
+ Critical placement rule:
662
+
663
+ - Register designer components in the admin/dashboard entry, such as `apps/cms/web/src/admin_dev/index.tsx`.
664
+ - Do not register designer-only components only in the public frontend entry, such as `apps/cms/web/src/index.tsx`, because the design page does not go through that public route. If registration only happens there, the admin designer will not see the component.
665
+
666
+ Agent rules:
667
+
668
+ - Use `cmsRegisterComponent()` in the admin dashboard bundle for components that must appear in the design sidebar.
669
+ - Use `cmsRegisterPage()` in the public frontend bundle for pages/frames/content that must be resolved during public CMS rendering.
670
+ - If a registered component is visible in public render but missing in the design sidebar, check whether it was registered in the admin dashboard entry.
671
+ - If a saved page contains `block-registered-component` but render says component not found, check whether the same component key was registered in the currently running bundle/process.
672
+
673
+ ### 6.9 Embedding CMS content inside coded components
674
+
675
+ Use `CmsEmbedContent` when coded components need to render CMS-managed content by ID. It accepts an `id` and optional `lang`, and follows the same language priority/fallback logic as CMS public rendering.
676
+
677
+ Use cases:
678
+
679
+ - coded page shells that embed CMS-managed regions
680
+ - coded landing pages with editable CMS sections
681
+ - admin/demo pages that need to preview CMS content by ID
682
+
683
+ Agent rules:
684
+
685
+ - Prefer `CmsEmbedContent` for embedding CMS content inside coded components.
686
+ - Pass `lang` explicitly only when the component must force a language. Otherwise let URL/browser/default language resolution work.
687
+ - Remember backend data fallback still follows `lang:id -> defaultLang:id -> id`.
688
+
689
+ ### 6.10 User-facing CMS troubleshooting checklist
690
+
691
+ When CMS routing/rendering does not behave as expected, check in this order:
692
+
693
+ 1. Does the public app entry call `pageRouter.use('*', CmsRenderPage)`?
694
+ 2. Is the URL parsed as `/lang/frame/content` or `/frame/content`?
695
+ 3. Is the `lang` segment present in `siteLangs`?
696
+ 4. Are `cmsPageDefault` and `cmsContentDefault` configured when frame/content are omitted?
697
+ 5. Does a coded `cmsRegisterPage(id, ...)` override the database page?
698
+ 6. Do the expected records exist as `lang:id`, `defaultLang:id`, or plain `id`?
699
+ 7. Was the page saved with the intended language-qualified ID?
700
+ 8. If using `My Components`, was the saved item checked as component?
701
+ 9. If using `Registered Components`, was `cmsRegisterComponent()` called in the admin/dashboard entry?
702
+ 10. If rendering a registered component, is the same key registered in the current runtime bundle?
703
+
704
+ ## 7. Coding Standards & Gotchas
464
705
 
465
706
  - **`useState` vs `HtmlVar`**: `useState` exists (`import { useState } from 'lupine.components'`) and is elegant for small components. But it rerenders the **entire** component — for large/complex components or high-frequency updates, prefer `HtmlVar` for surgical partial updates. `useEffect`, `useReducer`, `useCallback`, `useContext` **do NOT exist**.
466
707
  - **❌ `className`**: Use standard HTML `class`.
@@ -468,7 +709,7 @@ const Parent = () => {
468
709
  - **✅ Native Events**: `onClick`, `onChange`, `onInput`, `onMouseMove` etc. are standard HTML events and **ARE ALLOWED**. Use them for triggering logic or callbacks (e.g., `onInput={(e) => updateOtherThing(e.target.value)}`).
469
710
  - **✅ Uncontrolled Inputs**: While you _can_ use `onInput` to track state, the default efficient pattern is often to read `ref.$('input').value` only when the user clicks "Save" or "Search".
470
711
 
471
- ## 7. System Icons & Customization
712
+ ## 8. System Icons & Customization
472
713
 
473
714
  Lupine.components uses a set of built-in system icons (like `ma-close` and `mg-arrow_back_ios_new_outlined` found in components like `MobileHeaderWithBack`).
474
715
 
@@ -511,7 +752,7 @@ const css: CssProps = {
511
752
  };
512
753
  ```
513
754
 
514
- ## 8. Cross-Platform App Bootstrapping Guidance
755
+ ## 9. Cross-Platform App Bootstrapping Guidance
515
756
 
516
757
  When creating a new Cross-Platform App using `lupine.js`, follow this standard procedure for scaffolding the entry point, navigation, and icons:
517
758
 
@@ -533,7 +774,7 @@ When creating a new Cross-Platform App using `lupine.js`, follow this standard p
533
774
  5. **Local Storage Patterns**:
534
775
  For pure frontend utility apps (compatible with browsers, Capacitor, and Electron), wrap `localStorage.getItem()` and `localStorage.setItem()` inside dedicated Service singletons (e.g., `LocalNotesService`). Always parse/serialize consistently and assign standard unique IDs (like `Date.now()`) for newly inserted records. Combine this with the `onLoad` pattern inside `RefProps` to fetch data immediately when components render, injecting it directly into an `HtmlVar` wrapping the list.
535
776
 
536
- ## 9. Standard Mobile App Layout & Interactions
777
+ ## 10. Standard Mobile App Layout & Interactions
537
778
 
538
779
  When asked to "create a list page" or "initialize a standard mobile framework", rigorously apply this exact structural pattern based on the cross-platform starter app.
539
780
 
@@ -8,7 +8,7 @@
8
8
  <!--META-ENV-END-->
9
9
  <link rel="shortcut icon" href="{SUBDIR}/assets/favicon.ico?t={hash}" type="image/x-icon" />
10
10
  <link rel="stylesheet" type="text/css" href="{SUBDIR}/index.css?t={hash}" />
11
- <script defer src="{SUBDIR}/index.js#t={hash}"></script>
11
+ <script defer src="{SUBDIR}/index.js?t={hash}"></script>
12
12
  </head>
13
13
  <body>
14
14
  <div class="lupine-root"></div>
@@ -2,7 +2,6 @@ import {
2
2
  bindRouter,
3
3
  PageRouter,
4
4
  bindTheme,
5
- bindLang,
6
5
  setDefaultPageTitle,
7
6
  isFrontEnd,
8
7
  debugWatch,
@@ -17,8 +16,6 @@ import { baseCss } from './styles/base-css';
17
16
  if (isFrontEnd() && webEnv(ClientEnvKeys.NODE_ENV, '') === 'development') {
18
17
  debugWatch(webEnv(ClientEnvKeys.API_PORT, 0));
19
18
  }
20
-
21
- bindLang('en', {});
22
19
  bindTheme('light', pressThemes);
23
20
  bindAppGlobalStyle('comm-css', baseCss, false, true);
24
21
  setDefaultPageTitle('Lupine.js Doc');
@@ -58,7 +58,7 @@
58
58
  type="image/x-icon"
59
59
  />
60
60
  <link rel="stylesheet" type="text/css" href="{SUBDIR}/index.css?t={hash}" />
61
- <script defer src="{SUBDIR}/index.js#t={hash}"></script>
61
+ <script defer src="{SUBDIR}/index.js?t={hash}"></script>
62
62
  <!-- Remove Cloudflare Web Analytics if you clone this project -->
63
63
  <script
64
64
  defer
@@ -2,7 +2,6 @@ import {
2
2
  bindRouter,
3
3
  PageRouter,
4
4
  bindTheme,
5
- bindLang,
6
5
  setDefaultPageTitle,
7
6
  isFrontEnd,
8
7
  debugWatch,
@@ -17,8 +16,6 @@ import { markdownConfig } from '../markdown-built/markdown-config';
17
16
  if (isFrontEnd() && webEnv(ClientEnvKeys.NODE_ENV, '') === 'development') {
18
17
  debugWatch(webEnv(ClientEnvKeys.API_PORT, 0));
19
18
  }
20
-
21
- bindLang('en', {});
22
19
  bindTheme('light', pressThemes);
23
20
  bindAppGlobalStyle('comm-css', baseCss, false, true);
24
21
  setDefaultPageTitle('Doc Starter');
@@ -8,7 +8,7 @@
8
8
  <!--META-ENV-END-->
9
9
  <link rel="shortcut icon" href="{SUBDIR}/assets/favicon.ico?t={hash}" type="image/x-icon" />
10
10
  <link rel="stylesheet" type="text/css" href="{SUBDIR}/index.css?t={hash}" />
11
- <script defer src="{SUBDIR}/index.js#t={hash}"></script>
11
+ <script defer src="{SUBDIR}/index.js?t={hash}"></script>
12
12
  </head>
13
13
  <body>
14
14
  <div class="lupine-root"></div>
@@ -2,7 +2,6 @@ import {
2
2
  bindRouter,
3
3
  PageRouter,
4
4
  bindTheme,
5
- bindLang,
6
5
  setDefaultPageTitle,
7
6
  isFrontEnd,
8
7
  debugWatch,
@@ -17,8 +16,6 @@ import { baseCss } from './styles/base-css';
17
16
  if (isFrontEnd() && webEnv(ClientEnvKeys.NODE_ENV, '') === 'development') {
18
17
  debugWatch(webEnv(ClientEnvKeys.API_PORT, 0));
19
18
  }
20
-
21
- bindLang('en', {});
22
19
  bindTheme('light', pressThemes);
23
20
  bindAppGlobalStyle('comm-css', baseCss, false, true);
24
21
  setDefaultPageTitle('Lupine.js Doc');
@@ -12,7 +12,7 @@
12
12
  type="image/x-icon"
13
13
  />
14
14
  <link rel="stylesheet" type="text/css" href="{SUBDIR}/index.css?t={hash}" />
15
- <script defer src="{SUBDIR}/index.js#t={hash}"></script>
15
+ <script defer src="{SUBDIR}/index.js?t={hash}"></script>
16
16
  <!-- Cloudflare Web Analytics -->
17
17
  <script
18
18
  defer
@@ -2,7 +2,6 @@ import {
2
2
  bindRouter,
3
3
  PageRouter,
4
4
  bindTheme,
5
- bindLang,
6
5
  setDefaultPageTitle,
7
6
  isFrontEnd,
8
7
  debugWatch,
@@ -17,8 +16,6 @@ import { markdownConfig } from '../markdown-built/markdown-config';
17
16
  if (isFrontEnd() && webEnv(ClientEnvKeys.NODE_ENV, '') === 'development') {
18
17
  debugWatch(webEnv(ClientEnvKeys.API_PORT, 0));
19
18
  }
20
-
21
- bindLang('en', {});
22
19
  bindTheme('light', pressThemes);
23
20
  bindAppGlobalStyle('comm-css', baseCss, false, true);
24
21
  setDefaultPageTitle('Doc Starter');
@@ -8,7 +8,7 @@
8
8
  <!--META-ENV-END-->
9
9
  <link rel="shortcut icon" href="{SUBDIR}/assets/favicon.ico?t={hash}" type="image/x-icon" />
10
10
  <link rel="stylesheet" type="text/css" href="{SUBDIR}/index.css?t={hash}" />
11
- <script defer src="{SUBDIR}/index.js#t={hash}"></script>
11
+ <script defer src="{SUBDIR}/index.js?t={hash}"></script>
12
12
  </head>
13
13
  <body>
14
14
  <div class="lupine-root"></div>
@@ -8,7 +8,7 @@
8
8
  <!--META-ENV-END-->
9
9
  <link rel="shortcut icon" href="{SUBDIR}/assets/favicon.ico?t={hash}" type="image/x-icon" />
10
10
  <link rel="stylesheet" type="text/css" href="{SUBDIR}/index.css?t={hash}" />
11
- <script defer src="{SUBDIR}/index.js#t={hash}"></script>
11
+ <script defer src="{SUBDIR}/index.js?t={hash}"></script>
12
12
  </head>
13
13
  <body>
14
14
  <div class="lupine-root"></div>
@@ -9,7 +9,6 @@ import {
9
9
  isFrontEnd,
10
10
  PageRouter,
11
11
  webEnv,
12
- bindLang,
13
12
  bindTheme,
14
13
  setDefaultPageTitle,
15
14
  } from 'lupine.components';
@@ -21,8 +20,6 @@ import { FinancePage } from './pages/finance-page';
21
20
  import { AboutPage } from './pages/about-page';
22
21
  import { MinePage } from './pages/mine-page';
23
22
  import { ToolsPage } from './pages/tools-page';
24
-
25
- bindLang('zh-cn', {});
26
23
  bindTheme('light', themes);
27
24
  bindGlobalStyle('comm-css', baseCss, false, true);
28
25
  setDefaultPageTitle('Lupine.js Note Starter');
@@ -34,7 +34,7 @@
34
34
  <!--META-ENV-END-->
35
35
  <link rel="shortcut icon" href="{SUBDIR}/assets/favicon.ico?t={hash}" type="image/x-icon" />
36
36
  <link rel="stylesheet" type="text/css" href="{SUBDIR}/index.css?t={hash}" />
37
- <script defer src="{SUBDIR}/index.js#t={hash}"></script>
37
+ <script defer src="{SUBDIR}/index.js?t={hash}"></script>
38
38
  <!-- Remove Cloudflare Web Analytics if you clone this project -->
39
39
  <script
40
40
  defer
@@ -6,7 +6,6 @@ import {
6
6
  bindRouter,
7
7
  PageRouter,
8
8
  bindTheme,
9
- bindLang,
10
9
  setDefaultPageTitle,
11
10
  isFrontEnd,
12
11
  debugWatch,
@@ -21,8 +20,6 @@ import { HomePage } from '../pages/home-page';
21
20
  if (isFrontEnd() && webEnv('NODE_ENV', '') === 'development') {
22
21
  debugWatch(webEnv('API_PORT', 0));
23
22
  }
24
-
25
- bindLang('zh-cn', {});
26
23
  bindTheme('light', themes);
27
24
  bindGlobalStyle('comm-css', baseCss, false, true);
28
25
  setDefaultPageTitle('Lupine Template Responsive Starter');