nsbp-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +150 -0
  3. package/bin/nsbp.js +145 -0
  4. package/package.json +43 -0
  5. package/scripts/sync-template.js +277 -0
  6. package/templates/basic/.prettierignore +5 -0
  7. package/templates/basic/.prettierrc +5 -0
  8. package/templates/basic/README.md +13 -0
  9. package/templates/basic/package.json +101 -0
  10. package/templates/basic/postcss.config.js +7 -0
  11. package/templates/basic/public/favicon.ico +0 -0
  12. package/templates/basic/public/images/test/0.jpg +0 -0
  13. package/templates/basic/public/images/test/1.jpg +0 -0
  14. package/templates/basic/public/images/test/2.jpg +0 -0
  15. package/templates/basic/public/images/test/3.jpg +0 -0
  16. package/templates/basic/public/images/test/4.jpg +0 -0
  17. package/templates/basic/public/images/test/5.jpg +0 -0
  18. package/templates/basic/scripts/start.js +3 -0
  19. package/templates/basic/src/Routers.tsx +40 -0
  20. package/templates/basic/src/client/index.tsx +37 -0
  21. package/templates/basic/src/component/Header.tsx +38 -0
  22. package/templates/basic/src/component/Layout.tsx +24 -0
  23. package/templates/basic/src/component/Loading.tsx +7 -0
  24. package/templates/basic/src/component/Theme.tsx +14 -0
  25. package/templates/basic/src/containers/Home.tsx +435 -0
  26. package/templates/basic/src/containers/Login.tsx +32 -0
  27. package/templates/basic/src/containers/Photo.tsx +162 -0
  28. package/templates/basic/src/css/test.css +13 -0
  29. package/templates/basic/src/css/test.less +8 -0
  30. package/templates/basic/src/css/test2.sass +7 -0
  31. package/templates/basic/src/css/test3.scss +8 -0
  32. package/templates/basic/src/externals/less.d.ts +4 -0
  33. package/templates/basic/src/externals/window.d.ts +3 -0
  34. package/templates/basic/src/reducers/home.ts +17 -0
  35. package/templates/basic/src/reducers/index.ts +26 -0
  36. package/templates/basic/src/reducers/photo.ts +23 -0
  37. package/templates/basic/src/server/index.ts +29 -0
  38. package/templates/basic/src/server/photo.ts +118 -0
  39. package/templates/basic/src/server/utils.tsx +158 -0
  40. package/templates/basic/src/services/home.ts +52 -0
  41. package/templates/basic/src/services/photo.ts +64 -0
  42. package/templates/basic/src/store/constants.ts +4 -0
  43. package/templates/basic/src/store/index.ts +14 -0
  44. package/templates/basic/src/styled/common.ts +26 -0
  45. package/templates/basic/src/styled/component/header.ts +166 -0
  46. package/templates/basic/src/styled/component/layout.ts +10 -0
  47. package/templates/basic/src/styled/home.ts +789 -0
  48. package/templates/basic/src/styled/photo.ts +44 -0
  49. package/templates/basic/src/styled/test.ts +14 -0
  50. package/templates/basic/src/utils/clientConfig.ts +2 -0
  51. package/templates/basic/src/utils/config.ts +7 -0
  52. package/templates/basic/src/utils/fetch.ts +26 -0
  53. package/templates/basic/src/utils/index.ts +45 -0
  54. package/templates/basic/tsconfig.json +18 -0
  55. package/templates/basic/webpack.base.js +262 -0
  56. package/templates/basic/webpack.client.js +26 -0
  57. package/templates/basic/webpack.server.js +33 -0
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 同步 NSBP 主项目代码到 CLI 模板目录
4
+ * 使用:node scripts/sync-template.js
5
+ */
6
+
7
+ const fs = require('fs-extra');
8
+ const path = require('path');
9
+ const chalk = require('chalk');
10
+
11
+ // 路径配置
12
+ const ROOT_DIR = path.resolve(__dirname, '../..'); // nsbp 项目根目录
13
+ const CLI_DIR = path.resolve(__dirname, '..'); // cli 目录
14
+ const TARGET_DIR = path.join(CLI_DIR, 'templates/basic');
15
+
16
+ // 要复制的文件和目录列表
17
+ const COPY_ITEMS = [
18
+ 'src',
19
+ 'public',
20
+ 'scripts',
21
+ 'webpack.base.js',
22
+ 'webpack.client.js',
23
+ 'webpack.server.js',
24
+ 'tsconfig.json',
25
+ 'postcss.config.js',
26
+ '.gitignore',
27
+ '.prettierrc',
28
+ '.prettierignore',
29
+ 'README.md',
30
+ 'package.json'
31
+ ];
32
+
33
+ // 排除的模式(相对路径包含这些字符串的文件将被跳过)
34
+ const EXCLUDE_PATTERNS = [
35
+ '/node_modules/',
36
+ '/.temp_cache/',
37
+ '/build/',
38
+ '/.git/',
39
+ '.DS_Store',
40
+ '/.serena/',
41
+ '/.vscode/',
42
+ '/.idea/',
43
+ '.log',
44
+ '.tmp',
45
+ // 构建产物 - 匹配 public 下的特定文件
46
+ 'public/js/',
47
+ 'public/css/',
48
+ 'public/client.',
49
+ 'public/*.js',
50
+ 'public/*.js.map',
51
+ 'public/*.txt',
52
+ 'public/*.json'
53
+ ];
54
+
55
+ // 特殊文件处理配置
56
+ const SPECIAL_FILES = {
57
+ 'package.json': (content) => {
58
+ const pkg = JSON.parse(content);
59
+ // 修改为模板名称
60
+ pkg.name = 'nsbp-template';
61
+ pkg.description = 'node react ssr by webpack';
62
+ // 确保版本号
63
+ pkg.version = '1.0.0';
64
+ return JSON.stringify(pkg, null, 2);
65
+ }
66
+ };
67
+
68
+ // 主函数
69
+ async function syncTemplate() {
70
+ console.log(chalk.cyan('🔄 开始同步 NSBP 主项目代码到 CLI 模板...'));
71
+ console.log(chalk.gray(`源目录: ${ROOT_DIR}`));
72
+ console.log(chalk.gray(`目标目录: ${TARGET_DIR}`));
73
+
74
+ // 验证源目录
75
+ if (!fs.existsSync(ROOT_DIR)) {
76
+ console.error(chalk.red(`❌ 源目录不存在: ${ROOT_DIR}`));
77
+ process.exit(1);
78
+ }
79
+
80
+ // 清理目标目录(但保留 templates/basic 本身)
81
+ if (fs.existsSync(TARGET_DIR)) {
82
+ console.log(chalk.gray('🧹 清理目标目录...'));
83
+ await fs.emptyDir(TARGET_DIR);
84
+ } else {
85
+ await fs.ensureDir(TARGET_DIR);
86
+ }
87
+
88
+ // 复制文件
89
+ console.log(chalk.cyan('📦 复制项目文件...'));
90
+ let copiedCount = 0;
91
+ let skippedCount = 0;
92
+
93
+ for (const item of COPY_ITEMS) {
94
+ const source = path.join(ROOT_DIR, item);
95
+ const target = path.join(TARGET_DIR, item);
96
+
97
+ if (!fs.existsSync(source)) {
98
+ console.log(chalk.yellow(`⚠️ 源文件不存在,跳过: ${item}`));
99
+ skippedCount++;
100
+ continue;
101
+ }
102
+
103
+ // 检查是否为排除项
104
+ const shouldExclude = EXCLUDE_PATTERNS.some(pattern => {
105
+ if (pattern.includes('*')) {
106
+ // 简单的通配符匹配(仅支持后缀)
107
+ if (pattern.startsWith('*')) {
108
+ const ext = pattern.substring(1);
109
+ return item.endsWith(ext);
110
+ }
111
+ }
112
+ return item.includes(pattern);
113
+ });
114
+
115
+ if (shouldExclude) {
116
+ console.log(chalk.gray(` 跳过排除项: ${item}`));
117
+ skippedCount++;
118
+ continue;
119
+ }
120
+
121
+ try {
122
+ const stat = fs.statSync(source);
123
+
124
+ if (stat.isDirectory()) {
125
+ // 复制目录,使用自定义过滤器
126
+ await fs.copy(source, target, {
127
+ filter: (src) => {
128
+ const relativePath = path.relative(source, src);
129
+ // 检查是否匹配排除模式
130
+ for (const pattern of EXCLUDE_PATTERNS) {
131
+ if (relativePath.includes(pattern)) {
132
+ return false;
133
+ }
134
+ // 简单的通配符匹配
135
+ if (pattern.includes('*')) {
136
+ const [prefix] = pattern.split('*');
137
+ if (relativePath.startsWith(prefix)) {
138
+ return false;
139
+ }
140
+ }
141
+ }
142
+
143
+ // 排除构建产物文件(避免复制 .js, .js.map, .css, .css.map, .txt, .json 等)
144
+ // 仅对 public 目录应用此过滤
145
+ if (item === 'public') {
146
+ const buildArtifactPatterns = [
147
+ '.js.map',
148
+ '.css.map',
149
+ '.js',
150
+ '.css',
151
+ '.txt',
152
+ '.json',
153
+ '.LICENSE.txt'
154
+ ];
155
+
156
+ // 只检查文件,不检查目录
157
+ try {
158
+ const stat = fs.statSync(src);
159
+ if (stat.isFile()) {
160
+ const name = path.basename(src);
161
+
162
+ // 检查构建产物模式
163
+ for (const pattern of buildArtifactPatterns) {
164
+ if (name.endsWith(pattern)) {
165
+ // 但保留 favicon.ico
166
+ if (name === 'favicon.ico') {
167
+ return true;
168
+ }
169
+ return false;
170
+ }
171
+ }
172
+ }
173
+ } catch (err) {
174
+ // 如果无法获取状态,继续复制
175
+ console.warn(chalk.yellow(`⚠️ 无法检查文件状态: ${src}`), err.message);
176
+ }
177
+ }
178
+
179
+ return true;
180
+ }
181
+ });
182
+ console.log(chalk.green(` 目录复制: ${item}`));
183
+ } else {
184
+ // 复制文件
185
+ await fs.copy(source, target);
186
+
187
+ // 特殊文件处理
188
+ const basename = path.basename(item);
189
+ if (SPECIAL_FILES[basename]) {
190
+ const content = await fs.readFile(target, 'utf8');
191
+ const processed = SPECIAL_FILES[basename](content);
192
+ await fs.writeFile(target, processed, 'utf8');
193
+ console.log(chalk.green(` 文件处理: ${item} (已模板化)`));
194
+ } else {
195
+ console.log(chalk.green(` 文件复制: ${item}`));
196
+ }
197
+ }
198
+ copiedCount++;
199
+ } catch (error) {
200
+ console.error(chalk.red(`❌ 复制失败: ${item}`), error.message);
201
+ }
202
+ }
203
+
204
+ // 确保必要的目录存在(即使源目录为空)
205
+ const requiredDirs = ['public/css', 'public/js', 'public/images'];
206
+ for (const dir of requiredDirs) {
207
+ const dirPath = path.join(TARGET_DIR, dir);
208
+ if (!fs.existsSync(dirPath)) {
209
+ await fs.ensureDir(dirPath);
210
+ console.log(chalk.gray(` 创建目录: ${dir}`));
211
+ }
212
+ }
213
+
214
+ // 清理 public 目录中的构建产物
215
+ console.log(chalk.cyan('🧹 清理构建产物...'));
216
+ const publicDir = path.join(TARGET_DIR, 'public');
217
+ if (fs.existsSync(publicDir)) {
218
+ const items = await fs.readdir(publicDir, { withFileTypes: true });
219
+ for (const item of items) {
220
+ const itemPath = path.join(publicDir, item.name);
221
+ // 删除 .js, .js.map, .txt, .json 文件(但保留 favicon.ico 和目录)
222
+ if (item.isFile()) {
223
+ const ext = path.extname(item.name);
224
+ if (['.js', '.js.map', '.txt', '.json', '.css'].includes(ext) && item.name !== 'favicon.ico') {
225
+ await fs.remove(itemPath);
226
+ console.log(chalk.gray(` 删除: ${item.name}`));
227
+ }
228
+ }
229
+ }
230
+ // 清理子目录中的文件
231
+ const subDirs = ['css', 'js'];
232
+ for (const subDir of subDirs) {
233
+ const subDirPath = path.join(publicDir, subDir);
234
+ if (fs.existsSync(subDirPath)) {
235
+ const subItems = await fs.readdir(subDirPath, { withFileTypes: true });
236
+ for (const subItem of subItems) {
237
+ if (subItem.isFile()) {
238
+ await fs.remove(path.join(subDirPath, subItem.name));
239
+ console.log(chalk.gray(` 删除: ${subDir}/${subItem.name}`));
240
+ }
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ // 验证模板完整性
247
+ console.log(chalk.cyan('🔍 验证模板完整性...'));
248
+ const requiredFiles = ['src/Routers.tsx', 'scripts/start.js', 'package.json'];
249
+ let missingFiles = [];
250
+
251
+ for (const file of requiredFiles) {
252
+ if (!fs.existsSync(path.join(TARGET_DIR, file))) {
253
+ missingFiles.push(file);
254
+ }
255
+ }
256
+
257
+ if (missingFiles.length > 0) {
258
+ console.log(chalk.yellow(`⚠️ 缺少必要文件: ${missingFiles.join(', ')}`));
259
+ } else {
260
+ console.log(chalk.green('✅ 模板完整性验证通过'));
261
+ }
262
+
263
+ console.log(chalk.cyan('\n📊 同步完成'));
264
+ console.log(chalk.green(` 复制文件: ${copiedCount} 个`));
265
+ console.log(chalk.yellow(` 跳过文件: ${skippedCount} 个`));
266
+ console.log(chalk.blue(` 目标目录: ${TARGET_DIR}`));
267
+
268
+ if (missingFiles.length > 0) {
269
+ console.log(chalk.yellow('\n⚠️ 警告:部分必要文件缺失,请检查源项目'));
270
+ }
271
+ }
272
+
273
+ // 执行
274
+ syncTemplate().catch(error => {
275
+ console.error(chalk.red('❌ 同步过程中发生错误:'), error);
276
+ process.exit(1);
277
+ });
@@ -0,0 +1,5 @@
1
+ node_modules
2
+ build
3
+ yarn.lock
4
+ package-lock.json
5
+ public
@@ -0,0 +1,5 @@
1
+ {
2
+ "semi": false,
3
+ "singleQuote": true,
4
+ "trailingComma": "none"
5
+ }
@@ -0,0 +1,13 @@
1
+ # 开发
2
+ - npm run dev (开发运行)
3
+ - npm run build (生产编译)
4
+ - npm start (生产运行)
5
+
6
+ 客户端渲染
7
+ http://localhost:3001/
8
+
9
+ 服务端渲染
10
+ http://localhost:3001/?seo=1
11
+
12
+ 服务端渲染不成功,改为客户端渲染
13
+ http://localhost:3001/?seo=1&from=link
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "nsbp-template",
3
+ "version": "1.0.0",
4
+ "description": "node react ssr by webpack",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "dev": "npm-run-all -l -s dev:init -p dev:build:*",
8
+ "dev:init": "cross-env INIT=1 webpack --config webpack.server.js --mode development",
9
+ "dev:build:server": "webpack --config webpack.server.js --mode development --watch",
10
+ "dev:build:client": "webpack --config webpack.client.js --mode development --watch",
11
+ "dev:build:start": "node ./scripts/start.js",
12
+ "build": "npm run clean && npm-run-all -l -p build:**",
13
+ "build:server": "webpack --config webpack.server.js --mode production",
14
+ "build:client": "webpack --config webpack.client.js --mode production",
15
+ "start": "node ./scripts/start.js",
16
+ "clean": "rimraf build && rimraf public/js && rimraf public/css && rimraf public/client.* && rimraf public/*.js && rimraf public/*.js.map && rimraf public/*.txt && rimraf public/*.json && rimraf .temp_cache",
17
+ "format": "prettier --write **/*.{js,css,less,scss,ts,tsx}",
18
+ "test": "echo \"Error: no test specified\" && exit 1"
19
+ },
20
+ "keywords": [
21
+ "react",
22
+ "ssr",
23
+ "webpack",
24
+ "less",
25
+ "scss",
26
+ "sass",
27
+ "styled-components"
28
+ ],
29
+ "author": "Erishen Sun",
30
+ "license": "ISC",
31
+ "dependencies": {
32
+ "@loadable/component": "^5.15.0",
33
+ "@loadable/server": "^5.15.0",
34
+ "@redux-devtools/extension": "^3.3.0",
35
+ "@reduxjs/toolkit": "^2.11.2",
36
+ "axios": "^1.7.0",
37
+ "express": "^5.2.1",
38
+ "framer-motion": "^12.25.0",
39
+ "lodash": "^4.17.21",
40
+ "probe-image-size": "^7.1.0",
41
+ "react": "^19.2.3",
42
+ "react-dom": "^19.2.3",
43
+ "react-helmet": "^6.1.0",
44
+ "react-redux": "^9.2.0",
45
+ "react-router-dom": "^7.12.0",
46
+ "redux": "^5.0.1",
47
+ "redux-thunk": "^3.1.0",
48
+ "sass": "^1.80.0",
49
+ "serialize-javascript": "^7.0.2",
50
+ "styled-components": "^6.3.5"
51
+ },
52
+ "devDependencies": {
53
+ "@babel/core": "^7.25.0",
54
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
55
+ "@babel/plugin-transform-class-properties": "^7.25.0",
56
+ "@babel/plugin-transform-optional-chaining": "^7.25.0",
57
+ "@babel/preset-env": "^7.25.0",
58
+ "@babel/preset-react": "^7.24.0",
59
+ "@loadable/babel-plugin": "^5.13.2",
60
+ "@loadable/webpack-plugin": "^5.15.0",
61
+ "@types/express": "^5.0.6",
62
+ "@types/loadable__component": "^5.13.3",
63
+ "@types/loadable__server": "^5.12.5",
64
+ "@types/lodash": "^4.17.15",
65
+ "@types/probe-image-size": "^7.0.0",
66
+ "@types/react": "^19.2.8",
67
+ "@types/react-dom": "^19.2.3",
68
+ "@types/react-helmet": "^6.1.10",
69
+ "@types/react-router-dom": "^5.3.3",
70
+ "@types/serialize-javascript": "^5.0.3",
71
+ "@types/styled-components": "^5.1.36",
72
+ "babel-loader": "^10.0.0",
73
+ "babel-plugin-styled-components": "^2.1.4",
74
+ "browser-sync": "^3.0.4",
75
+ "browser-sync-webpack-plugin": "^2.3.0",
76
+ "cross-env": "^10.1.0",
77
+ "cross-spawn": "^7.0.6",
78
+ "css-loader": "^7.1.0",
79
+ "css-minimizer-webpack-plugin": "^7.0.0",
80
+ "html-webpack-plugin": "^5.6.0",
81
+ "less": "^4.2.0",
82
+ "less-loader": "^12.0.0",
83
+ "mini-css-extract-plugin": "^2.9.0",
84
+ "nodemon": "^3.1.0",
85
+ "npm-run-all": "^4.1.5",
86
+ "postcss-loader": "^8.1.0",
87
+ "postcss-preset-env": "^10.6.1",
88
+ "prettier": "^3.3.0",
89
+ "rimraf": "^6.1.2",
90
+ "sass-loader": "^16.0.0",
91
+ "style-loader": "^4.0.0",
92
+ "terser-webpack-plugin": "^5.3.0",
93
+ "ts-loader": "^9.5.0",
94
+ "typescript": "^5.0.0",
95
+ "typescript-loadable-components-plugin": "^1.0.2",
96
+ "webpack": "^5.96.0",
97
+ "webpack-cli": "^6.0.1",
98
+ "webpack-dev-server": "^5.1.0",
99
+ "webpack-node-externals": "^3.0.0"
100
+ }
101
+ }
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ plugins: [
3
+ require('postcss-preset-env')({
4
+ browsers: 'last 2 versions'
5
+ })
6
+ ]
7
+ }
@@ -0,0 +1,3 @@
1
+ const { version } = require('../package.json')
2
+
3
+ require(`../build/bundle.${version}.js`)
@@ -0,0 +1,40 @@
1
+ import React from 'react'
2
+ import { loadData as homeLoadData } from './services/home'
3
+ import { loadData as photoLoadData } from './services/photo'
4
+ import loadable from "@loadable/component"
5
+
6
+ const Loading = () => {
7
+ return <div>Loading...</div>
8
+ }
9
+
10
+ const Home = loadable(() => import("./containers/Home"), {
11
+ fallback: <Loading />
12
+ })
13
+
14
+ const Login = loadable(() => import("./containers/Login"), {
15
+ fallback: <Loading />
16
+ })
17
+
18
+ const Photo = loadable(() => import("./containers/Photo"), {
19
+ fallback: <Loading />
20
+ })
21
+
22
+ export default [
23
+ {
24
+ path: '/',
25
+ element: <Home />,
26
+ loadData: homeLoadData, //传入loadData方法
27
+ key: 'home'
28
+ },
29
+ {
30
+ path: '/login',
31
+ element: <Login />,
32
+ key: 'login'
33
+ },
34
+ {
35
+ path: '/photo',
36
+ element: <Photo />,
37
+ loadData: homeLoadData, // 使用相同的 loadData 来预取图片菜单
38
+ key: 'photo'
39
+ }
40
+ ]
@@ -0,0 +1,37 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { hydrateRoot } from 'react-dom/client'
3
+ import { BrowserRouter, Routes, Route } from 'react-router-dom'
4
+ import routers from '../Routers'
5
+ import { Provider } from 'react-redux'
6
+ import getStore from '../store'
7
+ import { isSEO } from '../utils'
8
+ import Theme from '../component/Theme'
9
+ import { loadableReady } from '@loadable/component'
10
+
11
+ const App = () => {
12
+ const [store, setStore] = useState(getStore())
13
+
14
+ useEffect(() => {
15
+ if (isSEO()) {
16
+ setStore(getStore(window?.context?.state))
17
+ }
18
+ }, [])
19
+
20
+ return (
21
+ <Theme>
22
+ <Provider store={store}>
23
+ <BrowserRouter>
24
+ <Routes>
25
+ {routers.map((router) => (
26
+ <Route key={router.key} path={router.path} element={router.element} />
27
+ ))}
28
+ </Routes>
29
+ </BrowserRouter>
30
+ </Provider>
31
+ </Theme>
32
+ )
33
+ }
34
+
35
+ loadableReady(() => {
36
+ const root = hydrateRoot(document.getElementById('root')!, <App />)
37
+ })
@@ -0,0 +1,38 @@
1
+ import React from 'react'
2
+ import { Link, useLocation } from 'react-router-dom'
3
+ import { Container, LogoWrapper as Logo, Nav, NavLink, Brand } from '../styled/component/header'
4
+
5
+ const Header = () => {
6
+ const location = useLocation()
7
+
8
+ return (
9
+ <Container>
10
+ <Brand>
11
+ <Link to="/">
12
+ <Logo>
13
+ <span>N</span>sbp.js
14
+ </Logo>
15
+ </Link>
16
+ </Brand>
17
+ <Nav>
18
+ <Link to="/">
19
+ <NavLink $active={location.pathname === '/'}>
20
+ 🏠 首页
21
+ </NavLink>
22
+ </Link>
23
+ <Link to="/login">
24
+ <NavLink $active={location.pathname === '/login'}>
25
+ 🔐 登录
26
+ </NavLink>
27
+ </Link>
28
+ <Link to="/photo">
29
+ <NavLink $active={location.pathname === '/photo'}>
30
+ 🖼️ 图片
31
+ </NavLink>
32
+ </Link>
33
+ </Nav>
34
+ </Container>
35
+ )
36
+ }
37
+
38
+ export default Header
@@ -0,0 +1,24 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import { Container } from '../styled/component/layout'
3
+ import Loading from './Loading'
4
+
5
+ const Layout = ({ children, query }: any) => {
6
+ let seo: any = 0
7
+ if (query) {
8
+ seo = query.seo
9
+ }
10
+
11
+ const [pageLoad, setPageLoad] = useState(
12
+ seo !== undefined ? parseInt(seo, 10) : 0
13
+ )
14
+
15
+ useEffect(() => {
16
+ if (pageLoad === 0) {
17
+ setPageLoad(1)
18
+ }
19
+ }, [])
20
+
21
+ return <Container>{pageLoad ? children : <Loading />}</Container>
22
+ }
23
+
24
+ export default Layout
@@ -0,0 +1,7 @@
1
+ import React from 'react'
2
+
3
+ const Loading = () => {
4
+ return <div>Loading...</div>
5
+ }
6
+
7
+ export default Loading
@@ -0,0 +1,14 @@
1
+ import React from 'react'
2
+ import { ThemeProvider } from 'styled-components'
3
+ import { GlobalStyle, theme, theme2 } from '../styled/common'
4
+
5
+ const Theme = ({ children }: any) => {
6
+ return (
7
+ <>
8
+ <GlobalStyle whiteColor={true} />
9
+ <ThemeProvider theme={theme2}>{children}</ThemeProvider>
10
+ </>
11
+ )
12
+ }
13
+
14
+ export default Theme