@navita/vite-plugin 1.0.1 → 2.0.1

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.cjs CHANGED
@@ -4,104 +4,72 @@ var fs = require('node:fs');
4
4
  var createRenderer = require('@navita/core/createRenderer');
5
5
  var css = require('@navita/css');
6
6
 
7
- function _interopNamespaceDefault(e) {
8
- var n = Object.create(null);
9
- if (e) {
10
- Object.keys(e).forEach(function (k) {
11
- if (k !== 'default') {
12
- var d = Object.getOwnPropertyDescriptor(e, k);
13
- Object.defineProperty(n, k, d.get ? d : {
14
- enumerable: true,
15
- get: function () { return e[k]; }
16
- });
17
- }
18
- });
19
- }
20
- n.default = e;
21
- return Object.freeze(n);
22
- }
23
-
24
- var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
25
-
26
- /*
27
- Some information for anyone wondering why we have duplicate css for the initial load
28
- during development in remix.
29
- https://github.com/remix-run/remix/discussions/8070#discussioncomment-7625870
30
- */ let renderer;
31
7
  const VIRTUAL_MODULE_ID = 'virtual:navita.css';
32
- const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
8
+ const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID.replace(/.css$/, '');
33
9
  function navita(options) {
34
10
  const importMap = [
35
11
  ...css.importMap,
36
12
  ...options?.importMap || []
37
13
  ];
38
14
  let server;
39
- let lastCssContent;
40
- let context;
41
- let isSSR = false;
42
- let isDEV = true;
15
+ let config;
16
+ let updateTimer = null;
17
+ let cssEmitted = false;
18
+ let isProduction = false;
43
19
  return {
44
- enforce: "pre",
45
- name: "navita",
46
- config (_, env) {
47
- isDEV = env.command === 'serve';
48
- return {
49
- optimizeDeps: {
50
- include: isDEV ? [
51
- '@navita/css'
52
- ] : []
53
- },
54
- ssr: {
55
- external: [
56
- '@navita/css',
57
- '@navita/adapter'
58
- ]
59
- }
60
- };
61
- },
62
- configResolved (config) {
63
- context = config.root;
64
- isSSR = !!config.build.ssr;
20
+ enforce: 'pre',
21
+ name: 'navita',
22
+ configResolved (_resolvedConfig) {
23
+ config = _resolvedConfig;
24
+ isProduction = config.mode === 'production';
65
25
  },
66
26
  configureServer (_server) {
67
- lastCssContent = undefined;
68
27
  server = _server;
69
28
  },
70
- async buildStart () {
71
- if (renderer) {
29
+ buildStart () {
30
+ if (getRenderer()) {
72
31
  return;
73
32
  }
74
- const defaultEngineOptions = {
75
- enableSourceMaps: isDEV,
76
- enableDebugIdentifiers: isDEV,
77
- ...options?.engineOptions || {}
78
- };
79
- renderer = createRenderer.createRenderer({
80
- context,
33
+ setRenderer(createRenderer.createRenderer({
34
+ context: config.root,
35
+ engineOptions: {
36
+ enableSourceMaps: !isProduction,
37
+ enableDebugIdentifiers: !isProduction,
38
+ ...options?.engineOptions || {}
39
+ },
81
40
  importMap,
82
- engineOptions: defaultEngineOptions,
83
41
  resolver: async (filepath, request)=>{
84
42
  const resolved = await this.resolve(request, filepath);
85
43
  return resolved?.id || null;
86
44
  },
87
45
  readFile: (path)=>{
88
- return fs__namespace.promises.readFile(path, 'utf-8');
46
+ return fs.promises.readFile(path, 'utf-8');
89
47
  }
90
- });
48
+ }));
91
49
  },
92
50
  resolveId (source) {
93
- if (source === VIRTUAL_MODULE_ID) {
51
+ const [id] = source.split('?');
52
+ if (id.endsWith(VIRTUAL_MODULE_ID)) {
94
53
  return RESOLVED_VIRTUAL_MODULE_ID;
95
54
  }
96
55
  },
97
56
  async load (source) {
98
- if (source === RESOLVED_VIRTUAL_MODULE_ID) {
99
- return renderer.engine.renderCssToString();
57
+ const [id] = source.split('?');
58
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
59
+ const css = getRenderer()?.engine.renderCssToString() || '';
60
+ return {
61
+ code: css,
62
+ map: {
63
+ mappings: ''
64
+ }
65
+ };
100
66
  }
101
- return;
102
67
  },
103
68
  async transform (code, id) {
104
- // Bail as early as we can
69
+ const renderer = getRenderer();
70
+ if (!renderer || id.includes('node_modules')) {
71
+ return null;
72
+ }
105
73
  if (!importMap.map((x)=>x.source).some((value)=>code.indexOf(value) !== -1)) {
106
74
  renderer.clearCache(id);
107
75
  return null;
@@ -110,52 +78,81 @@ function navita(options) {
110
78
  content: code,
111
79
  filePath: id
112
80
  });
113
- const newCssContent = renderer.engine.renderCssToString();
114
- if (lastCssContent !== newCssContent) {
115
- invalidateModule(RESOLVED_VIRTUAL_MODULE_ID);
116
- lastCssContent = newCssContent;
117
- for (const file of dependencies){
118
- if (!file.includes('node_modules')) {
119
- this.addWatchFile(file);
120
- }
121
- invalidateModule(file);
81
+ if (!isProduction) {
82
+ for (const dependency of dependencies){
83
+ this.addWatchFile(dependency);
122
84
  }
85
+ updateNavitaCSS();
123
86
  }
124
87
  return {
125
- code: `${result}\nimport "${VIRTUAL_MODULE_ID}";`,
88
+ code: result,
126
89
  map: sourceMap
127
90
  };
128
91
  },
92
+ transformIndexHtml: {
93
+ handler: async ()=>{
94
+ // If we're building, we don't want to inject the CSS into the HTML.
95
+ // We'll do this in the `renderChunk` hook instead.
96
+ if (isProduction) {
97
+ return [];
98
+ }
99
+ return [
100
+ {
101
+ tag: 'link',
102
+ injectTo: 'head',
103
+ attrs: {
104
+ rel: 'stylesheet',
105
+ href: `/${VIRTUAL_MODULE_ID}`
106
+ }
107
+ }
108
+ ];
109
+ }
110
+ },
129
111
  renderChunk (_, chunk) {
130
- if (isSSR) {
112
+ if (cssEmitted) {
131
113
  return;
132
114
  }
133
- for (const id of Object.keys(chunk.modules)){
134
- if (id.startsWith(RESOLVED_VIRTUAL_MODULE_ID)) {
135
- delete chunk.modules[id];
136
- }
137
- }
138
115
  chunk.viteMetadata.importedCss.add(this.getFileName(this.emitFile({
139
- name: "navita.css",
140
- type: "asset",
141
- source: renderer.engine.renderCssToString()
116
+ name: 'navita.css',
117
+ type: 'asset',
118
+ source: getRenderer()?.engine.renderCssToString()
142
119
  })));
120
+ cssEmitted = true;
143
121
  }
144
122
  };
145
- function invalidateModule(absoluteId) {
123
+ function updateNavitaCSS() {
146
124
  if (!server) {
147
125
  return;
148
126
  }
149
- const { moduleGraph } = server;
150
- const modules = moduleGraph.getModulesByFile(absoluteId);
151
- if (modules) {
152
- for (const module of modules){
153
- moduleGraph.invalidateModule(module);
154
- // Vite uses this timestamp to add `?t=` query string automatically for HMR.
155
- module.lastHMRTimestamp = module.lastInvalidationTimestamp || Date.now();
127
+ clearTimeout(updateTimer);
128
+ updateTimer = setTimeout(()=>{
129
+ const { moduleGraph , ws } = server;
130
+ const mod = moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
131
+ if (mod) {
132
+ moduleGraph.invalidateModule(mod);
133
+ ws.send({
134
+ type: 'update',
135
+ updates: [
136
+ {
137
+ type: 'css-update',
138
+ path: `/${VIRTUAL_MODULE_ID}`,
139
+ acceptedPath: `/${VIRTUAL_MODULE_ID}`,
140
+ timestamp: Date.now()
141
+ }
142
+ ]
143
+ });
156
144
  }
157
- }
145
+ }, 20);
158
146
  }
159
147
  }
148
+ const globalNavitaRendererKey = '__navita_renderer';
149
+ function setRenderer(renderer) {
150
+ globalThis[globalNavitaRendererKey] = renderer;
151
+ }
152
+ function getRenderer() {
153
+ return globalThis[globalNavitaRendererKey];
154
+ }
160
155
 
156
+ exports.VIRTUAL_MODULE_ID = VIRTUAL_MODULE_ID;
157
+ exports.getRenderer = getRenderer;
161
158
  exports.navita = navita;
package/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- import { ImportMap, EngineOptions } from '@navita/core/createRenderer';
1
+ import { ImportMap, EngineOptions, Renderer } from '@navita/core/createRenderer';
2
2
  import { Plugin } from 'vite';
3
3
 
4
+ declare const VIRTUAL_MODULE_ID = "virtual:navita.css";
4
5
  interface Options {
5
6
  importMap?: ImportMap;
6
7
  engineOptions?: EngineOptions;
7
8
  }
8
9
  declare function navita(options?: Options): Plugin;
10
+ declare function getRenderer(): Renderer | undefined;
9
11
 
10
- export { navita };
12
+ export { Options, VIRTUAL_MODULE_ID, getRenderer, navita };
package/index.mjs CHANGED
@@ -1,64 +1,41 @@
1
- import * as fs from 'node:fs';
1
+ import fs from 'node:fs';
2
2
  import { createRenderer } from '@navita/core/createRenderer';
3
3
  import { importMap } from '@navita/css';
4
4
 
5
- /*
6
- Some information for anyone wondering why we have duplicate css for the initial load
7
- during development in remix.
8
- https://github.com/remix-run/remix/discussions/8070#discussioncomment-7625870
9
- */ let renderer;
10
5
  const VIRTUAL_MODULE_ID = 'virtual:navita.css';
11
- const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
6
+ const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID.replace(/.css$/, '');
12
7
  function navita(options) {
13
8
  const importMap$1 = [
14
9
  ...importMap,
15
10
  ...options?.importMap || []
16
11
  ];
17
12
  let server;
18
- let lastCssContent;
19
- let context;
20
- let isSSR = false;
21
- let isDEV = true;
13
+ let config;
14
+ let updateTimer = null;
15
+ let cssEmitted = false;
16
+ let isProduction = false;
22
17
  return {
23
- enforce: "pre",
24
- name: "navita",
25
- config (_, env) {
26
- isDEV = env.command === 'serve';
27
- return {
28
- optimizeDeps: {
29
- include: isDEV ? [
30
- '@navita/css'
31
- ] : []
32
- },
33
- ssr: {
34
- external: [
35
- '@navita/css',
36
- '@navita/adapter'
37
- ]
38
- }
39
- };
40
- },
41
- configResolved (config) {
42
- context = config.root;
43
- isSSR = !!config.build.ssr;
18
+ enforce: 'pre',
19
+ name: 'navita',
20
+ configResolved (_resolvedConfig) {
21
+ config = _resolvedConfig;
22
+ isProduction = config.mode === 'production';
44
23
  },
45
24
  configureServer (_server) {
46
- lastCssContent = undefined;
47
25
  server = _server;
48
26
  },
49
- async buildStart () {
50
- if (renderer) {
27
+ buildStart () {
28
+ if (getRenderer()) {
51
29
  return;
52
30
  }
53
- const defaultEngineOptions = {
54
- enableSourceMaps: isDEV,
55
- enableDebugIdentifiers: isDEV,
56
- ...options?.engineOptions || {}
57
- };
58
- renderer = createRenderer({
59
- context,
31
+ setRenderer(createRenderer({
32
+ context: config.root,
33
+ engineOptions: {
34
+ enableSourceMaps: !isProduction,
35
+ enableDebugIdentifiers: !isProduction,
36
+ ...options?.engineOptions || {}
37
+ },
60
38
  importMap: importMap$1,
61
- engineOptions: defaultEngineOptions,
62
39
  resolver: async (filepath, request)=>{
63
40
  const resolved = await this.resolve(request, filepath);
64
41
  return resolved?.id || null;
@@ -66,21 +43,31 @@ function navita(options) {
66
43
  readFile: (path)=>{
67
44
  return fs.promises.readFile(path, 'utf-8');
68
45
  }
69
- });
46
+ }));
70
47
  },
71
48
  resolveId (source) {
72
- if (source === VIRTUAL_MODULE_ID) {
49
+ const [id] = source.split('?');
50
+ if (id.endsWith(VIRTUAL_MODULE_ID)) {
73
51
  return RESOLVED_VIRTUAL_MODULE_ID;
74
52
  }
75
53
  },
76
54
  async load (source) {
77
- if (source === RESOLVED_VIRTUAL_MODULE_ID) {
78
- return renderer.engine.renderCssToString();
55
+ const [id] = source.split('?');
56
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
57
+ const css = getRenderer()?.engine.renderCssToString() || '';
58
+ return {
59
+ code: css,
60
+ map: {
61
+ mappings: ''
62
+ }
63
+ };
79
64
  }
80
- return;
81
65
  },
82
66
  async transform (code, id) {
83
- // Bail as early as we can
67
+ const renderer = getRenderer();
68
+ if (!renderer || id.includes('node_modules')) {
69
+ return null;
70
+ }
84
71
  if (!importMap$1.map((x)=>x.source).some((value)=>code.indexOf(value) !== -1)) {
85
72
  renderer.clearCache(id);
86
73
  return null;
@@ -89,52 +76,79 @@ function navita(options) {
89
76
  content: code,
90
77
  filePath: id
91
78
  });
92
- const newCssContent = renderer.engine.renderCssToString();
93
- if (lastCssContent !== newCssContent) {
94
- invalidateModule(RESOLVED_VIRTUAL_MODULE_ID);
95
- lastCssContent = newCssContent;
96
- for (const file of dependencies){
97
- if (!file.includes('node_modules')) {
98
- this.addWatchFile(file);
99
- }
100
- invalidateModule(file);
79
+ if (!isProduction) {
80
+ for (const dependency of dependencies){
81
+ this.addWatchFile(dependency);
101
82
  }
83
+ updateNavitaCSS();
102
84
  }
103
85
  return {
104
- code: `${result}\nimport "${VIRTUAL_MODULE_ID}";`,
86
+ code: result,
105
87
  map: sourceMap
106
88
  };
107
89
  },
90
+ transformIndexHtml: {
91
+ handler: async ()=>{
92
+ // If we're building, we don't want to inject the CSS into the HTML.
93
+ // We'll do this in the `renderChunk` hook instead.
94
+ if (isProduction) {
95
+ return [];
96
+ }
97
+ return [
98
+ {
99
+ tag: 'link',
100
+ injectTo: 'head',
101
+ attrs: {
102
+ rel: 'stylesheet',
103
+ href: `/${VIRTUAL_MODULE_ID}`
104
+ }
105
+ }
106
+ ];
107
+ }
108
+ },
108
109
  renderChunk (_, chunk) {
109
- if (isSSR) {
110
+ if (cssEmitted) {
110
111
  return;
111
112
  }
112
- for (const id of Object.keys(chunk.modules)){
113
- if (id.startsWith(RESOLVED_VIRTUAL_MODULE_ID)) {
114
- delete chunk.modules[id];
115
- }
116
- }
117
113
  chunk.viteMetadata.importedCss.add(this.getFileName(this.emitFile({
118
- name: "navita.css",
119
- type: "asset",
120
- source: renderer.engine.renderCssToString()
114
+ name: 'navita.css',
115
+ type: 'asset',
116
+ source: getRenderer()?.engine.renderCssToString()
121
117
  })));
118
+ cssEmitted = true;
122
119
  }
123
120
  };
124
- function invalidateModule(absoluteId) {
121
+ function updateNavitaCSS() {
125
122
  if (!server) {
126
123
  return;
127
124
  }
128
- const { moduleGraph } = server;
129
- const modules = moduleGraph.getModulesByFile(absoluteId);
130
- if (modules) {
131
- for (const module of modules){
132
- moduleGraph.invalidateModule(module);
133
- // Vite uses this timestamp to add `?t=` query string automatically for HMR.
134
- module.lastHMRTimestamp = module.lastInvalidationTimestamp || Date.now();
125
+ clearTimeout(updateTimer);
126
+ updateTimer = setTimeout(()=>{
127
+ const { moduleGraph , ws } = server;
128
+ const mod = moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
129
+ if (mod) {
130
+ moduleGraph.invalidateModule(mod);
131
+ ws.send({
132
+ type: 'update',
133
+ updates: [
134
+ {
135
+ type: 'css-update',
136
+ path: `/${VIRTUAL_MODULE_ID}`,
137
+ acceptedPath: `/${VIRTUAL_MODULE_ID}`,
138
+ timestamp: Date.now()
139
+ }
140
+ ]
141
+ });
135
142
  }
136
- }
143
+ }, 20);
137
144
  }
138
145
  }
146
+ const globalNavitaRendererKey = '__navita_renderer';
147
+ function setRenderer(renderer) {
148
+ globalThis[globalNavitaRendererKey] = renderer;
149
+ }
150
+ function getRenderer() {
151
+ return globalThis[globalNavitaRendererKey];
152
+ }
139
153
 
140
- export { navita };
154
+ export { VIRTUAL_MODULE_ID, getRenderer, navita };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navita/vite-plugin",
3
- "version": "1.0.1",
3
+ "version": "2.0.1",
4
4
  "description": "Navita Vite Plugin",
5
5
  "keywords": [
6
6
  "vite",
@@ -14,6 +14,11 @@
14
14
  "import": "./index.mjs",
15
15
  "require": "./index.cjs",
16
16
  "types": "./index.d.ts"
17
+ },
18
+ "./remix": {
19
+ "import": "./remix.mjs",
20
+ "require": "./remix.cjs",
21
+ "types": "./remix.d.ts"
17
22
  }
18
23
  },
19
24
  "dependencies": {
package/remix.cjs ADDED
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('node:crypto');
4
+ var index = require('./index.cjs');
5
+
6
+ function _interopNamespaceDefault(e) {
7
+ var n = Object.create(null);
8
+ if (e) {
9
+ Object.keys(e).forEach(function (k) {
10
+ if (k !== 'default') {
11
+ var d = Object.getOwnPropertyDescriptor(e, k);
12
+ Object.defineProperty(n, k, d.get ? d : {
13
+ enumerable: true,
14
+ get: function () { return e[k]; }
15
+ });
16
+ }
17
+ });
18
+ }
19
+ n.default = e;
20
+ return Object.freeze(n);
21
+ }
22
+
23
+ var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
24
+
25
+ const remixServerBuildId = '\0virtual:remix/server-build';
26
+ let cssFileName;
27
+ function navitaRemix(options) {
28
+ let isProduction = false;
29
+ const { renderChunk , ...navitaVite } = index.navita(options);
30
+ return [
31
+ navitaVite,
32
+ {
33
+ name: 'navita-remix',
34
+ configResolved (config) {
35
+ isProduction = config.mode === 'production';
36
+ },
37
+ transform (code, id) {
38
+ if (isProduction || id !== remixServerBuildId) {
39
+ return;
40
+ }
41
+ return `${code}\n${remixServerBuildExtension}`;
42
+ },
43
+ renderChunk (_, chunk) {
44
+ if (chunk.name === "root") {
45
+ // Generate a random name for the CSS file.
46
+ // Vite uses a file hash as the name, but since the client build will finish before
47
+ // the server build, we need to generate a random name for the CSS file.
48
+ // Ideally we could use a hash, but since the server might contain more styles than the client, we need to do it like this.
49
+ const random = crypto__namespace.randomBytes(30).toString("base64").replace(/[^a-zA-Z0-9]/g, "").slice(0, 8);
50
+ cssFileName = `assets/navita-${random}.css`;
51
+ // Attach the file to the root chunk so that it's included in the client build.
52
+ chunk.viteMetadata?.importedCss.add(cssFileName);
53
+ return;
54
+ }
55
+ if (chunk.name === 'server-build') {
56
+ // In the server-build, we'll generate the CSS and emit it as an asset.
57
+ // Remix will then move it to the client assets.
58
+ this.emitFile({
59
+ fileName: cssFileName,
60
+ name: 'navita.css',
61
+ type: 'asset',
62
+ source: index.getRenderer()?.engine.renderCssToString()
63
+ });
64
+ }
65
+ }
66
+ }
67
+ ];
68
+ }
69
+ const remixServerBuildExtension = `
70
+ routes.root.module = {
71
+ ...route0,
72
+ links: () => [
73
+ ...(route0.links ? route0.links() : []),
74
+ { rel: 'stylesheet', href: '/${index.VIRTUAL_MODULE_ID}' },
75
+ ],
76
+ };
77
+ `;
78
+
79
+ exports.navitaRemix = navitaRemix;
package/remix.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Plugin } from 'vite';
2
+ import { Options } from './index.js';
3
+ import '@navita/core/createRenderer';
4
+
5
+ declare function navitaRemix(options?: Options): Plugin[];
6
+
7
+ export { navitaRemix };
package/remix.mjs ADDED
@@ -0,0 +1,58 @@
1
+ import * as crypto from 'node:crypto';
2
+ import { navita, getRenderer, VIRTUAL_MODULE_ID } from './index.mjs';
3
+
4
+ const remixServerBuildId = '\0virtual:remix/server-build';
5
+ let cssFileName;
6
+ function navitaRemix(options) {
7
+ let isProduction = false;
8
+ const { renderChunk , ...navitaVite } = navita(options);
9
+ return [
10
+ navitaVite,
11
+ {
12
+ name: 'navita-remix',
13
+ configResolved (config) {
14
+ isProduction = config.mode === 'production';
15
+ },
16
+ transform (code, id) {
17
+ if (isProduction || id !== remixServerBuildId) {
18
+ return;
19
+ }
20
+ return `${code}\n${remixServerBuildExtension}`;
21
+ },
22
+ renderChunk (_, chunk) {
23
+ if (chunk.name === "root") {
24
+ // Generate a random name for the CSS file.
25
+ // Vite uses a file hash as the name, but since the client build will finish before
26
+ // the server build, we need to generate a random name for the CSS file.
27
+ // Ideally we could use a hash, but since the server might contain more styles than the client, we need to do it like this.
28
+ const random = crypto.randomBytes(30).toString("base64").replace(/[^a-zA-Z0-9]/g, "").slice(0, 8);
29
+ cssFileName = `assets/navita-${random}.css`;
30
+ // Attach the file to the root chunk so that it's included in the client build.
31
+ chunk.viteMetadata?.importedCss.add(cssFileName);
32
+ return;
33
+ }
34
+ if (chunk.name === 'server-build') {
35
+ // In the server-build, we'll generate the CSS and emit it as an asset.
36
+ // Remix will then move it to the client assets.
37
+ this.emitFile({
38
+ fileName: cssFileName,
39
+ name: 'navita.css',
40
+ type: 'asset',
41
+ source: getRenderer()?.engine.renderCssToString()
42
+ });
43
+ }
44
+ }
45
+ }
46
+ ];
47
+ }
48
+ const remixServerBuildExtension = `
49
+ routes.root.module = {
50
+ ...route0,
51
+ links: () => [
52
+ ...(route0.links ? route0.links() : []),
53
+ { rel: 'stylesheet', href: '/${VIRTUAL_MODULE_ID}' },
54
+ ],
55
+ };
56
+ `;
57
+
58
+ export { navitaRemix };