@clikvn/showroom-visualizer 0.4.1-dev-11 → 0.4.1-dev-13
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/.claude/settings.local.json +19 -0
- package/CLAUDE.md +145 -145
- package/DEVELOPMENT.md +120 -120
- package/EXAMPLES.md +967 -967
- package/README.md +489 -489
- package/SETUP_COMPLETE.md +149 -149
- package/base.json +21 -21
- package/dist/components/SkinLayer/Floorplan/Minimap/test01.d.ts +15 -0
- package/dist/components/SkinLayer/Floorplan/Minimap/test01.d.ts.map +1 -0
- package/dist/components/SkinLayer/PoiDetailSlideIn/Detail.d.ts +1 -1
- package/dist/components/SkinLayer/PoiDetailSlideIn/Detail.d.ts.map +1 -1
- package/dist/components/SkinLayer/PoiDetailSlideIn/GroupActionButton.d.ts +1 -0
- package/dist/components/SkinLayer/PoiDetailSlideIn/GroupActionButton.d.ts.map +1 -1
- package/dist/fonts/icomoon.svg +633 -633
- package/dist/index.js +1 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +1 -1
- package/example/CSS_HANDLING.md +141 -141
- package/example/FIXES_SUMMARY.md +131 -121
- package/example/PATH_ALIASES.md +102 -103
- package/example/README.md +63 -64
- package/example/index.html +12 -13
- package/example/package.json +25 -25
- package/example/postcss.config.cjs +6 -6
- package/example/tailwind.config.cjs +12 -12
- package/example/tsconfig.node.json +11 -12
- package/example/vite.config.ts +142 -142
- package/package.json +133 -133
- package/rollup.config.js +400 -400
- package/tailwind.config.cjs +151 -151
- package/dist/components/SkinLayer/Drawer/PoiHeader/index.d.ts +0 -16
- package/dist/components/SkinLayer/Drawer/PoiHeader/index.d.ts.map +0 -1
- package/dist/components/SkinLayer/Drawer/index.d.ts +0 -29
- package/dist/components/SkinLayer/Drawer/index.d.ts.map +0 -1
- package/dist/components/SkinLayer/PlayAll/index.d.ts +0 -8
- package/dist/components/SkinLayer/PlayAll/index.d.ts.map +0 -1
- package/dist/features/VirtualTourVisualizer/index.d.ts +0 -20
- package/dist/features/VirtualTourVisualizer/index.d.ts.map +0 -1
- package/dist/features/VirtualTourVisualizerUI/index.d.ts +0 -17
- package/dist/features/VirtualTourVisualizerUI/index.d.ts.map +0 -1
- package/dist/index.html +0 -36
- /package/dist/features/ShowroomVisualizer/{cssStyles.d.ts → CssStyles.d.ts} +0 -0
- /package/dist/features/ShowroomVisualizer/{cssStyles.d.ts.map → CssStyles.d.ts.map} +0 -0
package/rollup.config.js
CHANGED
|
@@ -1,400 +1,400 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ============================================================================
|
|
3
|
-
* SHOWROOM VISUALIZER - ROLLUP BUILD CONFIGURATION
|
|
4
|
-
* ============================================================================
|
|
5
|
-
*
|
|
6
|
-
* 🎯 MỤC TIÊU:
|
|
7
|
-
* Tạo ra 2 build outputs phục vụ 2 use cases khác nhau:
|
|
8
|
-
*
|
|
9
|
-
* 1️⃣ NPM Package (dist/index.js) - EXTERNAL REACT
|
|
10
|
-
* - Dùng React từ host app (Next.js, React apps)
|
|
11
|
-
* - ✅ Hỗ trợ Custom Layout overrides
|
|
12
|
-
* - File nhỏ hơn (~1.8MB)
|
|
13
|
-
* - Use: import { ShowroomVisualizer } from '@clikvn/showroom-visualizer'
|
|
14
|
-
*
|
|
15
|
-
* 2️⃣ Web Component (dist/web.js) - BUNDLED REACT
|
|
16
|
-
* - Bundle React 19 vào file
|
|
17
|
-
* - ❌ KHÔNG hỗ trợ custom layout overrides
|
|
18
|
-
* - File lớn hơn (~2.0MB)
|
|
19
|
-
* - Use: <script type="module" src="web.js"></script>
|
|
20
|
-
*
|
|
21
|
-
* ────────────────────────────────────────────────────────────────────────────
|
|
22
|
-
*
|
|
23
|
-
* 📊 SO SÁNH:
|
|
24
|
-
*
|
|
25
|
-
* | Feature | NPM Package | Web Component |
|
|
26
|
-
* |----------------------|-------------|---------------|
|
|
27
|
-
* | React Source | Host app | Bundled |
|
|
28
|
-
* | Custom Layout | ✅ Yes | ❌ No |
|
|
29
|
-
* | File Size | ~1.8MB | ~2.0MB |
|
|
30
|
-
* | Dependencies | Need React | Standalone |
|
|
31
|
-
* | Use Case | React apps | Vanilla JS |
|
|
32
|
-
*
|
|
33
|
-
* ────────────────────────────────────────────────────────────────────────────
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
import resolve from '@rollup/plugin-node-resolve';
|
|
37
|
-
import commonjs from '@rollup/plugin-commonjs';
|
|
38
|
-
import typescript from '@rollup/plugin-typescript';
|
|
39
|
-
import { terser } from 'rollup-plugin-terser';
|
|
40
|
-
import postcss from 'rollup-plugin-postcss';
|
|
41
|
-
import { babel } from '@rollup/plugin-babel';
|
|
42
|
-
import autoprefixer from 'autoprefixer';
|
|
43
|
-
import { typescriptPaths } from 'rollup-plugin-typescript-paths';
|
|
44
|
-
import json from '@rollup/plugin-json';
|
|
45
|
-
import { uglify } from 'rollup-plugin-uglify';
|
|
46
|
-
import replace from 'rollup-plugin-replace';
|
|
47
|
-
import tailwindcss from 'tailwindcss';
|
|
48
|
-
import copy from '@rollup-extras/plugin-copy';
|
|
49
|
-
|
|
50
|
-
const extensions = ['.ts', '.tsx', '.js'];
|
|
51
|
-
const isDev =
|
|
52
|
-
process.env.NODE_ENV === 'development' || process.env.ROLLUP_WATCH === 'true';
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// CUSTOM PLUGIN: Fix React-DOM Imports
|
|
56
|
-
// ============================================================================
|
|
57
|
-
// React 18's ESM build không export findDOMNode, nhưng CommonJS build có.
|
|
58
|
-
// Plugin này rewrite code để chỉ dùng default import và access properties,
|
|
59
|
-
// tránh Rollup extract named imports.
|
|
60
|
-
const fixReactDomImports = () => ({
|
|
61
|
-
name: 'fix-react-dom-imports',
|
|
62
|
-
renderChunk(code) {
|
|
63
|
-
let fixed = code;
|
|
64
|
-
|
|
65
|
-
// Fix invalid variable declarations with dots in the name
|
|
66
|
-
// This pattern appears in minified code from third-party libraries
|
|
67
|
-
fixed = fixed.replace(
|
|
68
|
-
/var\s+_ReactDOM\$ReactDOM__default\.findDOMNode;/g,
|
|
69
|
-
'var _ReactDOM$findDOMNode;'
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// Fix all occurrences of the invalid variable name
|
|
73
|
-
fixed = fixed.replace(
|
|
74
|
-
/_ReactDOM\$ReactDOM__default\.findDOMNode/g,
|
|
75
|
-
'_ReactDOM$findDOMNode'
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
// Fix double ReactDOM__default references
|
|
79
|
-
fixed = fixed.replace(
|
|
80
|
-
/ReactDOM__default\.ReactDOM__default\./g,
|
|
81
|
-
'ReactDOM__default.'
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
// Additional fix for similar patterns with other methods
|
|
85
|
-
fixed = fixed.replace(
|
|
86
|
-
/var\s+_ReactDOM\$ReactDOM__default\.(\w+);/g,
|
|
87
|
-
'var _ReactDOM$$1;'
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
// Fix minified code that creates invalid variable names
|
|
91
|
-
fixed = fixed.replace(
|
|
92
|
-
/var\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\$([a-zA-Z_$][a-zA-Z0-9_$]*)\.[a-zA-Z_$][a-zA-Z0-9_$]*;/g,
|
|
93
|
-
function (match, prefix, suffix) {
|
|
94
|
-
return `var ${prefix}$${suffix}_temp;`;
|
|
95
|
-
}
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
// First, remove any findDOMNode from named imports
|
|
99
|
-
fixed = fixed.replace(
|
|
100
|
-
/import\s*\{([^}]*)\}\s*from\s*['"]react-dom['"]/g,
|
|
101
|
-
(match, imports) => {
|
|
102
|
-
if (imports.includes('findDOMNode')) {
|
|
103
|
-
const cleanedImports = imports
|
|
104
|
-
.split(',')
|
|
105
|
-
.map((imp) => imp.trim())
|
|
106
|
-
.filter((imp) => !imp.includes('findDOMNode'))
|
|
107
|
-
.join(', ');
|
|
108
|
-
|
|
109
|
-
if (cleanedImports.trim()) {
|
|
110
|
-
return `import { ${cleanedImports} } from 'react-dom'`;
|
|
111
|
-
}
|
|
112
|
-
return '';
|
|
113
|
-
}
|
|
114
|
-
return match;
|
|
115
|
-
}
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
// Handle combined default and named imports
|
|
119
|
-
fixed = fixed.replace(
|
|
120
|
-
/import\s+(\w+),\s*\{\s*([^}]+)\s*\}\s*from\s*['"]react-dom['"]/g,
|
|
121
|
-
(match, defaultImport, namedImports) => {
|
|
122
|
-
// Check if named imports contains invalid syntax like "ReactDOM__default.createPortal"
|
|
123
|
-
if (
|
|
124
|
-
namedImports.includes('ReactDOM__default.') ||
|
|
125
|
-
namedImports.includes('findDOMNode')
|
|
126
|
-
) {
|
|
127
|
-
// Extract valid imports only
|
|
128
|
-
const validImports = namedImports
|
|
129
|
-
.split(',')
|
|
130
|
-
.map((imp) => imp.trim())
|
|
131
|
-
.filter((imp) => {
|
|
132
|
-
// Remove any import that has dots or includes findDOMNode
|
|
133
|
-
return !imp.includes('.') && !imp.includes('findDOMNode');
|
|
134
|
-
})
|
|
135
|
-
.map((imp) => {
|
|
136
|
-
// Clean up "as" aliases
|
|
137
|
-
const parts = imp.split(/\s+as\s+/);
|
|
138
|
-
if (parts.length === 2 && !parts[0].includes('.')) {
|
|
139
|
-
return imp;
|
|
140
|
-
}
|
|
141
|
-
return '';
|
|
142
|
-
})
|
|
143
|
-
.filter(Boolean)
|
|
144
|
-
.join(', ');
|
|
145
|
-
|
|
146
|
-
if (validImports) {
|
|
147
|
-
return `import ${defaultImport}, { ${validImports} } from 'react-dom'`;
|
|
148
|
-
}
|
|
149
|
-
return `import ${defaultImport} from 'react-dom'`;
|
|
150
|
-
}
|
|
151
|
-
return match;
|
|
152
|
-
}
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
// Replace all variations of findDOMNode (but not in function declarations)
|
|
156
|
-
fixed = fixed.replace(/\bfindDOMNode\$?\d*\b/g, (match, offset) => {
|
|
157
|
-
// Check if this is a function declaration
|
|
158
|
-
const beforeMatch = fixed.substring(Math.max(0, offset - 20), offset);
|
|
159
|
-
if (beforeMatch.trim().endsWith('function')) {
|
|
160
|
-
return match; // Don't replace in function declarations
|
|
161
|
-
}
|
|
162
|
-
return 'ReactDOM__default.findDOMNode';
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// Fix property access patterns
|
|
166
|
-
fixed = fixed.replace(/(\w+)\.findDOMNode\b/g, (match, obj) => {
|
|
167
|
-
if (obj === 'ReactDOM__default') return match;
|
|
168
|
-
return 'ReactDOM__default.findDOMNode';
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Replace other methods - but NOT when they're being used in import statements
|
|
172
|
-
const methodsToReplace = [
|
|
173
|
-
'createPortal',
|
|
174
|
-
'unstable_batchedUpdates',
|
|
175
|
-
'flushSync',
|
|
176
|
-
];
|
|
177
|
-
|
|
178
|
-
methodsToReplace.forEach((method) => {
|
|
179
|
-
// Only replace when not in import context
|
|
180
|
-
const regex = new RegExp(
|
|
181
|
-
`\\b${method}\\$?\\d*\\b(?![:\\.]|\\s*as\\s*\\w+)`,
|
|
182
|
-
'g'
|
|
183
|
-
);
|
|
184
|
-
fixed = fixed.replace(regex, (match, offset) => {
|
|
185
|
-
// Check if this is within an import statement
|
|
186
|
-
const beforeMatch = fixed.substring(Math.max(0, offset - 100), offset);
|
|
187
|
-
if (beforeMatch.includes('import') && beforeMatch.includes('{')) {
|
|
188
|
-
return match;
|
|
189
|
-
}
|
|
190
|
-
return `ReactDOM__default.${method}`;
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
return { code: fixed, map: null };
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// ============================================================================
|
|
199
|
-
// CONFIG CHO NPM PACKAGE - External React
|
|
200
|
-
// ============================================================================
|
|
201
|
-
// Dùng khi: import { ShowroomVisualizer } from '@clikvn/showroom-visualizer'
|
|
202
|
-
// - React được external → Dùng React version của host app (Next.js, React app)
|
|
203
|
-
// - Cho phép custom layout overrides vì share cùng React instance
|
|
204
|
-
// - File nhỏ hơn vì không bundle React
|
|
205
|
-
const indexConfig = {
|
|
206
|
-
context: 'this',
|
|
207
|
-
|
|
208
|
-
// ✅ External React - Library sẽ dùng React từ host app
|
|
209
|
-
external: ['react', 'react-dom', 'react/jsx-runtime', 'react-dom/client'],
|
|
210
|
-
|
|
211
|
-
onwarn(warning, warn) {
|
|
212
|
-
// Bỏ qua warning về "use client" directive
|
|
213
|
-
if (
|
|
214
|
-
warning.code === 'MODULE_LEVEL_DIRECTIVE' &&
|
|
215
|
-
warning.message.includes('"use client"')
|
|
216
|
-
) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
// Hiển thị các warning khác
|
|
220
|
-
warn(warning);
|
|
221
|
-
},
|
|
222
|
-
plugins: [
|
|
223
|
-
resolve({
|
|
224
|
-
extensions,
|
|
225
|
-
browser: true,
|
|
226
|
-
preferBuiltins: false,
|
|
227
|
-
dedupe: [
|
|
228
|
-
'react',
|
|
229
|
-
'react-dom',
|
|
230
|
-
'useTranslation',
|
|
231
|
-
'i18n',
|
|
232
|
-
'I18nextProvider',
|
|
233
|
-
],
|
|
234
|
-
}),
|
|
235
|
-
commonjs({
|
|
236
|
-
esmExternals: true,
|
|
237
|
-
requireReturnsDefault: 'auto',
|
|
238
|
-
// Không extract named imports từ external modules
|
|
239
|
-
ignoreDynamicRequires: false,
|
|
240
|
-
}),
|
|
241
|
-
// Skip uglify in dev mode - tốn thời gian
|
|
242
|
-
// uglify does not support ES2015+ properly — and React’s ESM builds rely heavily on ES2015+ semantics
|
|
243
|
-
// !isDev && uglify(),
|
|
244
|
-
json(),
|
|
245
|
-
// Skip babel in dev mode - giảm thời gian compile
|
|
246
|
-
!isDev &&
|
|
247
|
-
babel({
|
|
248
|
-
babelHelpers: 'bundled',
|
|
249
|
-
exclude: 'node_modules/**',
|
|
250
|
-
extensions,
|
|
251
|
-
}),
|
|
252
|
-
replace({
|
|
253
|
-
'process.env.NODE_ENV': JSON.stringify(
|
|
254
|
-
isDev ? 'development' : 'production'
|
|
255
|
-
),
|
|
256
|
-
}),
|
|
257
|
-
postcss({
|
|
258
|
-
plugins: [autoprefixer(), tailwindcss()],
|
|
259
|
-
extract: false,
|
|
260
|
-
modules: false,
|
|
261
|
-
autoModules: false,
|
|
262
|
-
minimize: !isDev, // Skip minify CSS trong dev
|
|
263
|
-
inject: false,
|
|
264
|
-
}),
|
|
265
|
-
typescript({
|
|
266
|
-
sourceMap: isDev, // Source map chỉ trong dev
|
|
267
|
-
inlineSources: isDev,
|
|
268
|
-
}),
|
|
269
|
-
typescriptPaths({ preserveExtensions: true }),
|
|
270
|
-
// Skip terser trong dev - tốn rất nhiều thời gian
|
|
271
|
-
!isDev && terser({ output: { comments: false } }),
|
|
272
|
-
// ⚠️ QUAN TRỌNG: Fix react-dom imports sau khi bundle
|
|
273
|
-
// Phải đặt ở cuối để chạy sau tất cả transformations
|
|
274
|
-
fixReactDomImports(),
|
|
275
|
-
].filter(Boolean), // Remove false values
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
// ============================================================================
|
|
279
|
-
// CONFIG CHO WEB COMPONENT - Bundled React
|
|
280
|
-
// ============================================================================
|
|
281
|
-
// Dùng khi: <script src="web.js"> hoặc dynamic import('web.js')
|
|
282
|
-
// - React được BUNDLE vào → Standalone, không cần dependencies
|
|
283
|
-
// - KHÔNG hỗ trợ custom layout overrides (vì khác React instance với host app)
|
|
284
|
-
// - File lớn hơn vì bundle React (~200KB extra)
|
|
285
|
-
// - Phù hợp cho vanilla JS/HTML projects
|
|
286
|
-
const browserConfig = {
|
|
287
|
-
context: 'this',
|
|
288
|
-
// ❌ KHÔNG external React - Bundle React version 19 vào file
|
|
289
|
-
// external: [], // Không khai báo external = bundle tất cả
|
|
290
|
-
onwarn: indexConfig.onwarn,
|
|
291
|
-
plugins: [
|
|
292
|
-
resolve({
|
|
293
|
-
browser: true,
|
|
294
|
-
extensions: ['.ts', '.tsx', '.js'],
|
|
295
|
-
dedupe: ['react', 'react-dom'],
|
|
296
|
-
preferBuiltins: false,
|
|
297
|
-
}),
|
|
298
|
-
|
|
299
|
-
commonjs({
|
|
300
|
-
esmExternals: true,
|
|
301
|
-
requireReturnsDefault: 'auto',
|
|
302
|
-
}),
|
|
303
|
-
|
|
304
|
-
json(),
|
|
305
|
-
!isDev &&
|
|
306
|
-
babel({
|
|
307
|
-
babelHelpers: 'bundled',
|
|
308
|
-
extensions: ['.ts', '.tsx', '.js'],
|
|
309
|
-
exclude: 'node_modules/**',
|
|
310
|
-
}),
|
|
311
|
-
|
|
312
|
-
replace({
|
|
313
|
-
'process.env.NODE_ENV': JSON.stringify(
|
|
314
|
-
isDev ? 'development' : 'production'
|
|
315
|
-
),
|
|
316
|
-
preventAssignment: true,
|
|
317
|
-
}),
|
|
318
|
-
|
|
319
|
-
postcss({
|
|
320
|
-
plugins: [autoprefixer(), tailwindcss()],
|
|
321
|
-
extract: false,
|
|
322
|
-
modules: false,
|
|
323
|
-
autoModules: false,
|
|
324
|
-
minimize: !isDev,
|
|
325
|
-
inject: false,
|
|
326
|
-
}),
|
|
327
|
-
|
|
328
|
-
typescript({
|
|
329
|
-
sourceMap: isDev,
|
|
330
|
-
inlineSources: isDev,
|
|
331
|
-
}),
|
|
332
|
-
|
|
333
|
-
typescriptPaths({ preserveExtensions: true }),
|
|
334
|
-
!isDev &&
|
|
335
|
-
terser({
|
|
336
|
-
format: { comments: false },
|
|
337
|
-
}),
|
|
338
|
-
].filter(Boolean),
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
// ============================================================================
|
|
342
|
-
// DUAL BUILD STRATEGY
|
|
343
|
-
// ============================================================================
|
|
344
|
-
const configs = [
|
|
345
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
346
|
-
// BUILD 1: NPM Package (dist/index.js)
|
|
347
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
348
|
-
// 📦 Sử dụng: import { ShowroomVisualizer } from '@clikvn/showroom-visualizer'
|
|
349
|
-
//
|
|
350
|
-
// ✅ Ưu điểm:
|
|
351
|
-
// - Dùng React version của host app (Next.js, React app)
|
|
352
|
-
// - Cho phép SWIZZLE components (share React instance)
|
|
353
|
-
// - File nhỏ (~1.8MB) vì không bundle React
|
|
354
|
-
//
|
|
355
|
-
// 📝 Use cases:
|
|
356
|
-
// - React/Next.js applications
|
|
357
|
-
// - Cần customize components qua custom layout overrides
|
|
358
|
-
// - Muốn kiểm soát React version
|
|
359
|
-
{
|
|
360
|
-
input: './src/index.ts', // ⭐ IMPORTANT: Do NOT bundle react / react-dom
|
|
361
|
-
external: ['react', 'react-dom', 'react/jsx-runtime'],
|
|
362
|
-
output: {
|
|
363
|
-
file: 'dist/index.js',
|
|
364
|
-
format: 'esm',
|
|
365
|
-
inlineDynamicImports: true,
|
|
366
|
-
},
|
|
367
|
-
...indexConfig, // ✅ External React dependencies
|
|
368
|
-
},
|
|
369
|
-
|
|
370
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
371
|
-
// BUILD 2: Web Component (dist/web.js)
|
|
372
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
373
|
-
// 🌐 Sử dụng: <script type="module" src="web.js"></script>
|
|
374
|
-
// hoặc: await import('web.js')
|
|
375
|
-
//
|
|
376
|
-
// ✅ Ưu điểm:
|
|
377
|
-
// - Standalone, không cần install dependencies
|
|
378
|
-
// - Hoạt động trong vanilla JS/HTML
|
|
379
|
-
// - Không cần build tools
|
|
380
|
-
//
|
|
381
|
-
// ❌ Hạn chế:
|
|
382
|
-
// - KHÔNG hỗ trợ custom layout overrides (khác React instance)
|
|
383
|
-
// - File lớn hơn (~2.0MB) vì bundle React
|
|
384
|
-
//
|
|
385
|
-
// 📝 Use cases:
|
|
386
|
-
// - Vanilla JS/HTML projects
|
|
387
|
-
// - Không cần customize UI
|
|
388
|
-
// - Quick integration
|
|
389
|
-
{
|
|
390
|
-
input: './src/web.ts',
|
|
391
|
-
output: {
|
|
392
|
-
file: 'dist/web.js',
|
|
393
|
-
format: 'esm',
|
|
394
|
-
inlineDynamicImports: true,
|
|
395
|
-
},
|
|
396
|
-
...browserConfig, // ✅ Bundle React version 19
|
|
397
|
-
},
|
|
398
|
-
];
|
|
399
|
-
|
|
400
|
-
export default configs;
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* SHOWROOM VISUALIZER - ROLLUP BUILD CONFIGURATION
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 🎯 MỤC TIÊU:
|
|
7
|
+
* Tạo ra 2 build outputs phục vụ 2 use cases khác nhau:
|
|
8
|
+
*
|
|
9
|
+
* 1️⃣ NPM Package (dist/index.js) - EXTERNAL REACT
|
|
10
|
+
* - Dùng React từ host app (Next.js, React apps)
|
|
11
|
+
* - ✅ Hỗ trợ Custom Layout overrides
|
|
12
|
+
* - File nhỏ hơn (~1.8MB)
|
|
13
|
+
* - Use: import { ShowroomVisualizer } from '@clikvn/showroom-visualizer'
|
|
14
|
+
*
|
|
15
|
+
* 2️⃣ Web Component (dist/web.js) - BUNDLED REACT
|
|
16
|
+
* - Bundle React 19 vào file
|
|
17
|
+
* - ❌ KHÔNG hỗ trợ custom layout overrides
|
|
18
|
+
* - File lớn hơn (~2.0MB)
|
|
19
|
+
* - Use: <script type="module" src="web.js"></script>
|
|
20
|
+
*
|
|
21
|
+
* ────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
*
|
|
23
|
+
* 📊 SO SÁNH:
|
|
24
|
+
*
|
|
25
|
+
* | Feature | NPM Package | Web Component |
|
|
26
|
+
* |----------------------|-------------|---------------|
|
|
27
|
+
* | React Source | Host app | Bundled |
|
|
28
|
+
* | Custom Layout | ✅ Yes | ❌ No |
|
|
29
|
+
* | File Size | ~1.8MB | ~2.0MB |
|
|
30
|
+
* | Dependencies | Need React | Standalone |
|
|
31
|
+
* | Use Case | React apps | Vanilla JS |
|
|
32
|
+
*
|
|
33
|
+
* ────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import resolve from '@rollup/plugin-node-resolve';
|
|
37
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
38
|
+
import typescript from '@rollup/plugin-typescript';
|
|
39
|
+
import { terser } from 'rollup-plugin-terser';
|
|
40
|
+
import postcss from 'rollup-plugin-postcss';
|
|
41
|
+
import { babel } from '@rollup/plugin-babel';
|
|
42
|
+
import autoprefixer from 'autoprefixer';
|
|
43
|
+
import { typescriptPaths } from 'rollup-plugin-typescript-paths';
|
|
44
|
+
import json from '@rollup/plugin-json';
|
|
45
|
+
import { uglify } from 'rollup-plugin-uglify';
|
|
46
|
+
import replace from 'rollup-plugin-replace';
|
|
47
|
+
import tailwindcss from 'tailwindcss';
|
|
48
|
+
import copy from '@rollup-extras/plugin-copy';
|
|
49
|
+
|
|
50
|
+
const extensions = ['.ts', '.tsx', '.js'];
|
|
51
|
+
const isDev =
|
|
52
|
+
process.env.NODE_ENV === 'development' || process.env.ROLLUP_WATCH === 'true';
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// CUSTOM PLUGIN: Fix React-DOM Imports
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// React 18's ESM build không export findDOMNode, nhưng CommonJS build có.
|
|
58
|
+
// Plugin này rewrite code để chỉ dùng default import và access properties,
|
|
59
|
+
// tránh Rollup extract named imports.
|
|
60
|
+
const fixReactDomImports = () => ({
|
|
61
|
+
name: 'fix-react-dom-imports',
|
|
62
|
+
renderChunk(code) {
|
|
63
|
+
let fixed = code;
|
|
64
|
+
|
|
65
|
+
// Fix invalid variable declarations with dots in the name
|
|
66
|
+
// This pattern appears in minified code from third-party libraries
|
|
67
|
+
fixed = fixed.replace(
|
|
68
|
+
/var\s+_ReactDOM\$ReactDOM__default\.findDOMNode;/g,
|
|
69
|
+
'var _ReactDOM$findDOMNode;'
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Fix all occurrences of the invalid variable name
|
|
73
|
+
fixed = fixed.replace(
|
|
74
|
+
/_ReactDOM\$ReactDOM__default\.findDOMNode/g,
|
|
75
|
+
'_ReactDOM$findDOMNode'
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Fix double ReactDOM__default references
|
|
79
|
+
fixed = fixed.replace(
|
|
80
|
+
/ReactDOM__default\.ReactDOM__default\./g,
|
|
81
|
+
'ReactDOM__default.'
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Additional fix for similar patterns with other methods
|
|
85
|
+
fixed = fixed.replace(
|
|
86
|
+
/var\s+_ReactDOM\$ReactDOM__default\.(\w+);/g,
|
|
87
|
+
'var _ReactDOM$$1;'
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Fix minified code that creates invalid variable names
|
|
91
|
+
fixed = fixed.replace(
|
|
92
|
+
/var\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\$([a-zA-Z_$][a-zA-Z0-9_$]*)\.[a-zA-Z_$][a-zA-Z0-9_$]*;/g,
|
|
93
|
+
function (match, prefix, suffix) {
|
|
94
|
+
return `var ${prefix}$${suffix}_temp;`;
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// First, remove any findDOMNode from named imports
|
|
99
|
+
fixed = fixed.replace(
|
|
100
|
+
/import\s*\{([^}]*)\}\s*from\s*['"]react-dom['"]/g,
|
|
101
|
+
(match, imports) => {
|
|
102
|
+
if (imports.includes('findDOMNode')) {
|
|
103
|
+
const cleanedImports = imports
|
|
104
|
+
.split(',')
|
|
105
|
+
.map((imp) => imp.trim())
|
|
106
|
+
.filter((imp) => !imp.includes('findDOMNode'))
|
|
107
|
+
.join(', ');
|
|
108
|
+
|
|
109
|
+
if (cleanedImports.trim()) {
|
|
110
|
+
return `import { ${cleanedImports} } from 'react-dom'`;
|
|
111
|
+
}
|
|
112
|
+
return '';
|
|
113
|
+
}
|
|
114
|
+
return match;
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Handle combined default and named imports
|
|
119
|
+
fixed = fixed.replace(
|
|
120
|
+
/import\s+(\w+),\s*\{\s*([^}]+)\s*\}\s*from\s*['"]react-dom['"]/g,
|
|
121
|
+
(match, defaultImport, namedImports) => {
|
|
122
|
+
// Check if named imports contains invalid syntax like "ReactDOM__default.createPortal"
|
|
123
|
+
if (
|
|
124
|
+
namedImports.includes('ReactDOM__default.') ||
|
|
125
|
+
namedImports.includes('findDOMNode')
|
|
126
|
+
) {
|
|
127
|
+
// Extract valid imports only
|
|
128
|
+
const validImports = namedImports
|
|
129
|
+
.split(',')
|
|
130
|
+
.map((imp) => imp.trim())
|
|
131
|
+
.filter((imp) => {
|
|
132
|
+
// Remove any import that has dots or includes findDOMNode
|
|
133
|
+
return !imp.includes('.') && !imp.includes('findDOMNode');
|
|
134
|
+
})
|
|
135
|
+
.map((imp) => {
|
|
136
|
+
// Clean up "as" aliases
|
|
137
|
+
const parts = imp.split(/\s+as\s+/);
|
|
138
|
+
if (parts.length === 2 && !parts[0].includes('.')) {
|
|
139
|
+
return imp;
|
|
140
|
+
}
|
|
141
|
+
return '';
|
|
142
|
+
})
|
|
143
|
+
.filter(Boolean)
|
|
144
|
+
.join(', ');
|
|
145
|
+
|
|
146
|
+
if (validImports) {
|
|
147
|
+
return `import ${defaultImport}, { ${validImports} } from 'react-dom'`;
|
|
148
|
+
}
|
|
149
|
+
return `import ${defaultImport} from 'react-dom'`;
|
|
150
|
+
}
|
|
151
|
+
return match;
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// Replace all variations of findDOMNode (but not in function declarations)
|
|
156
|
+
fixed = fixed.replace(/\bfindDOMNode\$?\d*\b/g, (match, offset) => {
|
|
157
|
+
// Check if this is a function declaration
|
|
158
|
+
const beforeMatch = fixed.substring(Math.max(0, offset - 20), offset);
|
|
159
|
+
if (beforeMatch.trim().endsWith('function')) {
|
|
160
|
+
return match; // Don't replace in function declarations
|
|
161
|
+
}
|
|
162
|
+
return 'ReactDOM__default.findDOMNode';
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Fix property access patterns
|
|
166
|
+
fixed = fixed.replace(/(\w+)\.findDOMNode\b/g, (match, obj) => {
|
|
167
|
+
if (obj === 'ReactDOM__default') return match;
|
|
168
|
+
return 'ReactDOM__default.findDOMNode';
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Replace other methods - but NOT when they're being used in import statements
|
|
172
|
+
const methodsToReplace = [
|
|
173
|
+
'createPortal',
|
|
174
|
+
'unstable_batchedUpdates',
|
|
175
|
+
'flushSync',
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
methodsToReplace.forEach((method) => {
|
|
179
|
+
// Only replace when not in import context
|
|
180
|
+
const regex = new RegExp(
|
|
181
|
+
`\\b${method}\\$?\\d*\\b(?![:\\.]|\\s*as\\s*\\w+)`,
|
|
182
|
+
'g'
|
|
183
|
+
);
|
|
184
|
+
fixed = fixed.replace(regex, (match, offset) => {
|
|
185
|
+
// Check if this is within an import statement
|
|
186
|
+
const beforeMatch = fixed.substring(Math.max(0, offset - 100), offset);
|
|
187
|
+
if (beforeMatch.includes('import') && beforeMatch.includes('{')) {
|
|
188
|
+
return match;
|
|
189
|
+
}
|
|
190
|
+
return `ReactDOM__default.${method}`;
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return { code: fixed, map: null };
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// CONFIG CHO NPM PACKAGE - External React
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Dùng khi: import { ShowroomVisualizer } from '@clikvn/showroom-visualizer'
|
|
202
|
+
// - React được external → Dùng React version của host app (Next.js, React app)
|
|
203
|
+
// - Cho phép custom layout overrides vì share cùng React instance
|
|
204
|
+
// - File nhỏ hơn vì không bundle React
|
|
205
|
+
const indexConfig = {
|
|
206
|
+
context: 'this',
|
|
207
|
+
|
|
208
|
+
// ✅ External React - Library sẽ dùng React từ host app
|
|
209
|
+
external: ['react', 'react-dom', 'react/jsx-runtime', 'react-dom/client'],
|
|
210
|
+
|
|
211
|
+
onwarn(warning, warn) {
|
|
212
|
+
// Bỏ qua warning về "use client" directive
|
|
213
|
+
if (
|
|
214
|
+
warning.code === 'MODULE_LEVEL_DIRECTIVE' &&
|
|
215
|
+
warning.message.includes('"use client"')
|
|
216
|
+
) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Hiển thị các warning khác
|
|
220
|
+
warn(warning);
|
|
221
|
+
},
|
|
222
|
+
plugins: [
|
|
223
|
+
resolve({
|
|
224
|
+
extensions,
|
|
225
|
+
browser: true,
|
|
226
|
+
preferBuiltins: false,
|
|
227
|
+
dedupe: [
|
|
228
|
+
'react',
|
|
229
|
+
'react-dom',
|
|
230
|
+
'useTranslation',
|
|
231
|
+
'i18n',
|
|
232
|
+
'I18nextProvider',
|
|
233
|
+
],
|
|
234
|
+
}),
|
|
235
|
+
commonjs({
|
|
236
|
+
esmExternals: true,
|
|
237
|
+
requireReturnsDefault: 'auto',
|
|
238
|
+
// Không extract named imports từ external modules
|
|
239
|
+
ignoreDynamicRequires: false,
|
|
240
|
+
}),
|
|
241
|
+
// Skip uglify in dev mode - tốn thời gian
|
|
242
|
+
// uglify does not support ES2015+ properly — and React’s ESM builds rely heavily on ES2015+ semantics
|
|
243
|
+
// !isDev && uglify(),
|
|
244
|
+
json(),
|
|
245
|
+
// Skip babel in dev mode - giảm thời gian compile
|
|
246
|
+
!isDev &&
|
|
247
|
+
babel({
|
|
248
|
+
babelHelpers: 'bundled',
|
|
249
|
+
exclude: 'node_modules/**',
|
|
250
|
+
extensions,
|
|
251
|
+
}),
|
|
252
|
+
replace({
|
|
253
|
+
'process.env.NODE_ENV': JSON.stringify(
|
|
254
|
+
isDev ? 'development' : 'production'
|
|
255
|
+
),
|
|
256
|
+
}),
|
|
257
|
+
postcss({
|
|
258
|
+
plugins: [autoprefixer(), tailwindcss()],
|
|
259
|
+
extract: false,
|
|
260
|
+
modules: false,
|
|
261
|
+
autoModules: false,
|
|
262
|
+
minimize: !isDev, // Skip minify CSS trong dev
|
|
263
|
+
inject: false,
|
|
264
|
+
}),
|
|
265
|
+
typescript({
|
|
266
|
+
sourceMap: isDev, // Source map chỉ trong dev
|
|
267
|
+
inlineSources: isDev,
|
|
268
|
+
}),
|
|
269
|
+
typescriptPaths({ preserveExtensions: true }),
|
|
270
|
+
// Skip terser trong dev - tốn rất nhiều thời gian
|
|
271
|
+
!isDev && terser({ output: { comments: false } }),
|
|
272
|
+
// ⚠️ QUAN TRỌNG: Fix react-dom imports sau khi bundle
|
|
273
|
+
// Phải đặt ở cuối để chạy sau tất cả transformations
|
|
274
|
+
fixReactDomImports(),
|
|
275
|
+
].filter(Boolean), // Remove false values
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// CONFIG CHO WEB COMPONENT - Bundled React
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// Dùng khi: <script src="web.js"> hoặc dynamic import('web.js')
|
|
282
|
+
// - React được BUNDLE vào → Standalone, không cần dependencies
|
|
283
|
+
// - KHÔNG hỗ trợ custom layout overrides (vì khác React instance với host app)
|
|
284
|
+
// - File lớn hơn vì bundle React (~200KB extra)
|
|
285
|
+
// - Phù hợp cho vanilla JS/HTML projects
|
|
286
|
+
const browserConfig = {
|
|
287
|
+
context: 'this',
|
|
288
|
+
// ❌ KHÔNG external React - Bundle React version 19 vào file
|
|
289
|
+
// external: [], // Không khai báo external = bundle tất cả
|
|
290
|
+
onwarn: indexConfig.onwarn,
|
|
291
|
+
plugins: [
|
|
292
|
+
resolve({
|
|
293
|
+
browser: true,
|
|
294
|
+
extensions: ['.ts', '.tsx', '.js'],
|
|
295
|
+
dedupe: ['react', 'react-dom'],
|
|
296
|
+
preferBuiltins: false,
|
|
297
|
+
}),
|
|
298
|
+
|
|
299
|
+
commonjs({
|
|
300
|
+
esmExternals: true,
|
|
301
|
+
requireReturnsDefault: 'auto',
|
|
302
|
+
}),
|
|
303
|
+
|
|
304
|
+
json(),
|
|
305
|
+
!isDev &&
|
|
306
|
+
babel({
|
|
307
|
+
babelHelpers: 'bundled',
|
|
308
|
+
extensions: ['.ts', '.tsx', '.js'],
|
|
309
|
+
exclude: 'node_modules/**',
|
|
310
|
+
}),
|
|
311
|
+
|
|
312
|
+
replace({
|
|
313
|
+
'process.env.NODE_ENV': JSON.stringify(
|
|
314
|
+
isDev ? 'development' : 'production'
|
|
315
|
+
),
|
|
316
|
+
preventAssignment: true,
|
|
317
|
+
}),
|
|
318
|
+
|
|
319
|
+
postcss({
|
|
320
|
+
plugins: [autoprefixer(), tailwindcss()],
|
|
321
|
+
extract: false,
|
|
322
|
+
modules: false,
|
|
323
|
+
autoModules: false,
|
|
324
|
+
minimize: !isDev,
|
|
325
|
+
inject: false,
|
|
326
|
+
}),
|
|
327
|
+
|
|
328
|
+
typescript({
|
|
329
|
+
sourceMap: isDev,
|
|
330
|
+
inlineSources: isDev,
|
|
331
|
+
}),
|
|
332
|
+
|
|
333
|
+
typescriptPaths({ preserveExtensions: true }),
|
|
334
|
+
!isDev &&
|
|
335
|
+
terser({
|
|
336
|
+
format: { comments: false },
|
|
337
|
+
}),
|
|
338
|
+
].filter(Boolean),
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// DUAL BUILD STRATEGY
|
|
343
|
+
// ============================================================================
|
|
344
|
+
const configs = [
|
|
345
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
346
|
+
// BUILD 1: NPM Package (dist/index.js)
|
|
347
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
348
|
+
// 📦 Sử dụng: import { ShowroomVisualizer } from '@clikvn/showroom-visualizer'
|
|
349
|
+
//
|
|
350
|
+
// ✅ Ưu điểm:
|
|
351
|
+
// - Dùng React version của host app (Next.js, React app)
|
|
352
|
+
// - Cho phép SWIZZLE components (share React instance)
|
|
353
|
+
// - File nhỏ (~1.8MB) vì không bundle React
|
|
354
|
+
//
|
|
355
|
+
// 📝 Use cases:
|
|
356
|
+
// - React/Next.js applications
|
|
357
|
+
// - Cần customize components qua custom layout overrides
|
|
358
|
+
// - Muốn kiểm soát React version
|
|
359
|
+
{
|
|
360
|
+
input: './src/index.ts', // ⭐ IMPORTANT: Do NOT bundle react / react-dom
|
|
361
|
+
external: ['react', 'react-dom', 'react/jsx-runtime'],
|
|
362
|
+
output: {
|
|
363
|
+
file: 'dist/index.js',
|
|
364
|
+
format: 'esm',
|
|
365
|
+
inlineDynamicImports: true,
|
|
366
|
+
},
|
|
367
|
+
...indexConfig, // ✅ External React dependencies
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
371
|
+
// BUILD 2: Web Component (dist/web.js)
|
|
372
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
373
|
+
// 🌐 Sử dụng: <script type="module" src="web.js"></script>
|
|
374
|
+
// hoặc: await import('web.js')
|
|
375
|
+
//
|
|
376
|
+
// ✅ Ưu điểm:
|
|
377
|
+
// - Standalone, không cần install dependencies
|
|
378
|
+
// - Hoạt động trong vanilla JS/HTML
|
|
379
|
+
// - Không cần build tools
|
|
380
|
+
//
|
|
381
|
+
// ❌ Hạn chế:
|
|
382
|
+
// - KHÔNG hỗ trợ custom layout overrides (khác React instance)
|
|
383
|
+
// - File lớn hơn (~2.0MB) vì bundle React
|
|
384
|
+
//
|
|
385
|
+
// 📝 Use cases:
|
|
386
|
+
// - Vanilla JS/HTML projects
|
|
387
|
+
// - Không cần customize UI
|
|
388
|
+
// - Quick integration
|
|
389
|
+
{
|
|
390
|
+
input: './src/web.ts',
|
|
391
|
+
output: {
|
|
392
|
+
file: 'dist/web.js',
|
|
393
|
+
format: 'esm',
|
|
394
|
+
inlineDynamicImports: true,
|
|
395
|
+
},
|
|
396
|
+
...browserConfig, // ✅ Bundle React version 19
|
|
397
|
+
},
|
|
398
|
+
];
|
|
399
|
+
|
|
400
|
+
export default configs;
|