@norejs/ssr-builder 0.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.
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const program = require('commander');
4
+
5
+ program.command('start').action((options, command) => {
6
+ require('../scripts/start')(options, 'development');
7
+ });
8
+
9
+ program.command('build').action((options, command) => {
10
+ require('../scripts/start')(options, 'production');
11
+ });
12
+
13
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@norejs/ssr-builder",
3
+ "version": "0.0.1",
4
+ "description": "SSR builder for NoreJS",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "norejs-ssr-builder": "./bin/norejs-ssr.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "author": "",
13
+ "license": "ISC",
14
+ "dependencies": {
15
+ "@norejs/ssr-server": "^0.0.1",
16
+ "commander": "10",
17
+ "fs-extra": "^11.1.1",
18
+ "webpack-merge": "^5.9.0",
19
+ "webpack-node-externals": "^3.0.0"
20
+ }
21
+ }
@@ -0,0 +1,75 @@
1
+ const path = require('path');
2
+ const nodeExternals = require('webpack-node-externals');
3
+
4
+ const { merge } = require('webpack-merge');
5
+ const { requireNpmFromCwd } = require('../../../utils/project');
6
+ // 去掉HTMLWebpackPlugin
7
+ const ssrDisablePlugins = [
8
+ 'HtmlWebpackPlugin',
9
+ 'InlineChunkHtmlPlugin',
10
+ 'HotModuleReplacementPlugin',
11
+ 'ManifestPlugin',
12
+ 'ReactRefreshWebpackPlugin',
13
+ ];
14
+
15
+ const MiniCssExtractPlugin = requireNpmFromCwd('mini-css-extract-plugin');
16
+
17
+ module.exports = function (baseConfig, ssrConfig, webpackEnv = 'development') {
18
+ // 去掉多余的插件
19
+ baseConfig.plugins = baseConfig.plugins.filter((plugin) => {
20
+ return !ssrDisablePlugins.includes(plugin.constructor.name);
21
+ });
22
+ if (
23
+ baseConfig.plugins.find(
24
+ (plugin) => plugin.constructor.name === 'MiniCssExtractPlugin'
25
+ )
26
+ ) {
27
+ baseConfig.plugins.push(
28
+ new MiniCssExtractPlugin({
29
+ // Options similar to the same options in webpackOptions.output
30
+ // both options are optional
31
+ filename: 'static/css/[name].[contenthash:8].css',
32
+ chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
33
+ })
34
+ );
35
+ }
36
+
37
+ const projectRoot = process.cwd();
38
+ // 替换style-loader
39
+ const rules = baseConfig.module.rules[1].oneOf;
40
+ rules.forEach((rule) => {
41
+ if (rule.test?.toString().includes('css')) {
42
+ rule.use.forEach((use, index) => {
43
+ if (typeof use === 'string' && use.includes('style-loader')) {
44
+ rule.use[index] = {
45
+ loader: MiniCssExtractPlugin.loader,
46
+ options: {},
47
+ };
48
+ } else if (use.loader?.includes('style-loader')) {
49
+ rule.use[index] = {
50
+ loader: MiniCssExtractPlugin.loader,
51
+ options: {},
52
+ };
53
+ }
54
+ });
55
+ }
56
+ return rule;
57
+ });
58
+
59
+ // 读取env文件,或者自定义
60
+ return merge(baseConfig, {
61
+ entry: { main: '/' + ssrConfig.entry },
62
+ output: {
63
+ filename: 'server.js',
64
+ path: path.resolve(projectRoot, ssrConfig.dist),
65
+ libraryTarget: 'commonjs',
66
+ },
67
+ optimization: {
68
+ minimize: false,
69
+ splitChunks: false,
70
+ runtimeChunk: false,
71
+ },
72
+ target: 'node',
73
+ externals: [nodeExternals()],
74
+ });
75
+ };
@@ -0,0 +1,64 @@
1
+ const fs = require('fs-extra');
2
+ const {
3
+ getNpmPathFromCwd,
4
+ getFilePathFromCwd,
5
+ } = require('../../utils/project');
6
+
7
+ const runWebpack = require('../../utils/runWebpack');
8
+
9
+ module.exports = function start(
10
+ options,
11
+ ssrConfig,
12
+ webpackEnv = 'development'
13
+ ) {
14
+ process.env.NODE_ENV = webpackEnv;
15
+ process.env.BABEL_ENV = webpackEnv;
16
+ // 获取react-scripts 的配置
17
+ const projectWebpackConfigPath = getWebpackFile('webpack.config.js');
18
+ const baseWebpackConfig = require(projectWebpackConfigPath)(webpackEnv);
19
+ const ssrWebpackConfigFactory = getSSRWebpackConfig();
20
+ if (typeof ssrWebpackConfigFactory !== 'function') {
21
+ throw new Error('webpack.ssr.config.js should export a function');
22
+ }
23
+ const webpackConfig = ssrWebpackConfigFactory(
24
+ baseWebpackConfig,
25
+ ssrConfig,
26
+ webpackEnv
27
+ );
28
+ return runWebpack(webpackConfig, webpackEnv);
29
+ };
30
+ /**
31
+ * 获取ssr的webpack配置
32
+ * @returns
33
+ */
34
+ function getSSRWebpackConfig() {
35
+ const webpackConfigPath = getFilePathFromCwd(
36
+ 'config/webpack.ssr.config.js'
37
+ );
38
+ if (fs.existsSync(webpackConfigPath)) {
39
+ return require(webpackConfigPath);
40
+ }
41
+
42
+ return require('./config/webpack.ssr.config');
43
+ }
44
+
45
+ /**
46
+ * 获取项目中的webpack配置
47
+ * */
48
+ function getWebpackFile(webpackFileName) {
49
+ const projectWebpackConfigPath = getFilePathFromCwd(
50
+ 'config/' + webpackFileName
51
+ );
52
+ if (fs.existsSync(projectWebpackConfigPath)) {
53
+ return projectWebpackConfigPath;
54
+ }
55
+ const webpackConfigPath = getNpmPathFromCwd(
56
+ 'react-scripts/config/' + webpackFileName
57
+ );
58
+ if (!fs.existsSync(webpackConfigPath)) {
59
+ throw new Error(
60
+ 'react-scripts is not installed in your project please make sure you have installed it'
61
+ );
62
+ }
63
+ return webpackConfigPath;
64
+ }
@@ -0,0 +1,11 @@
1
+ // 读取项目中的配置,监测当前项目是否react-scripts 项目
2
+ const { getProjectConfig } = require('../utils/project');
3
+
4
+ module.exports = function start(options, webpackEnv = 'development') {
5
+ const config = getProjectConfig();
6
+ if (config.ssr) {
7
+ const builderName = config.ssr.builder || 'react-scripts-ssr';
8
+ const builder = require(`../plugins/${builderName}`);
9
+ return builder(options, config.ssr, webpackEnv);
10
+ }
11
+ };
@@ -0,0 +1,128 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+ const defaultConfig = {
4
+ ssr: {
5
+ entry: 'src/server.js',
6
+ dist: 'build-ssr',
7
+ builder: 'react-scripts-ssr',
8
+ },
9
+ csr: {
10
+ dist: 'build',
11
+ },
12
+ };
13
+ /**
14
+ * 获取项目中的npm包路径
15
+ * @param {*} npmPkgName
16
+ * @param {*} filePath
17
+ * @returns
18
+ */
19
+ function getNpmPathFromCwd(npmPkgName) {
20
+ const projectRoot = process.cwd();
21
+ const nodeModules = path.resolve(projectRoot, 'node_modules');
22
+ const npmPkgPath = path.resolve(nodeModules, npmPkgName);
23
+ return npmPkgPath;
24
+ }
25
+
26
+ /**
27
+ *
28
+ * @param {*} filePath
29
+ * @returns
30
+ */
31
+ function getFilePathFromCwd(filePath) {
32
+ const projectRoot = process.cwd();
33
+ return path.resolve(projectRoot, filePath);
34
+ }
35
+
36
+ async function isReactScriptsInstalled() {
37
+ const reactScriptsPath = getNpmPathFromCwd('react-scripts');
38
+ return await fs.exists(reactScriptsPath);
39
+ }
40
+ function getProjectConfig() {
41
+ const projectConfigPath = getFilePathFromCwd('nore.config.js');
42
+ let customConfig = {};
43
+ if (fs.existsSync(projectConfigPath)) {
44
+ customConfig = require(projectConfigPath) || {};
45
+ }
46
+ // 深度合并
47
+ // 深度合并
48
+
49
+ return deepAssign({}, defaultConfig, customConfig);
50
+ }
51
+
52
+ function isPlainObject(obj) {
53
+ return (
54
+ typeof obj === 'object' &&
55
+ Object.prototype.toString.call(obj) === '[object Object]'
56
+ );
57
+ }
58
+ function deepAssign() {
59
+ let len = arguments.length,
60
+ target = arguments[0];
61
+ if (!isPlainObject(target)) {
62
+ target = {};
63
+ }
64
+ for (let i = 1; i < len; i++) {
65
+ let source = arguments[i];
66
+ if (isPlainObject(source)) {
67
+ for (let s in source) {
68
+ if (s === '__proto__' || target === source[s]) {
69
+ continue;
70
+ }
71
+ if (isPlainObject(source[s])) {
72
+ target[s] = deepAssign(target[s], source[s]);
73
+ } else {
74
+ target[s] = source[s];
75
+ }
76
+ }
77
+ }
78
+ }
79
+ return target;
80
+ }
81
+
82
+ /**
83
+ * 当前项目是否运行过eject
84
+ * @param {*} projectRoot
85
+ * @returns
86
+ */
87
+ async function isEjected(projectRoot) {
88
+ const configPath = path.resolve(projectRoot, 'config');
89
+ const scriptsPath = path.resolve(projectRoot, 'scripts');
90
+ return (await fs.exists(configPath)) && (await fs.exists(scriptsPath));
91
+ }
92
+ /**
93
+ * 从项目中加载npm包
94
+ * @param {*} npmPkgName
95
+ * @returns
96
+ */
97
+ function requireNpmFromCwd(npmPkgName) {
98
+ const nodeModules = getNpmPathFromCwd(npmPkgName);
99
+ try {
100
+ return require(nodeModules);
101
+ } catch (error) {
102
+ return undefined;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * 从项目中加载某个文件
108
+ * @param {*} filename
109
+ * @returns
110
+ */
111
+ function requireFromCwd(filename) {
112
+ const nodeModules = getFilePathFromCwd(filename);
113
+ try {
114
+ return require(nodeModules);
115
+ } catch (error) {
116
+ return undefined;
117
+ }
118
+ }
119
+
120
+ module.exports = {
121
+ getNpmPathFromCwd,
122
+ isEjected,
123
+ requireNpmFromCwd,
124
+ getFilePathFromCwd,
125
+ requireFromCwd,
126
+ getProjectConfig,
127
+ isReactScriptsInstalled,
128
+ };
@@ -0,0 +1,140 @@
1
+ const { requireNpmFromCwd } = require('./project');
2
+ const formatWebpackMessages = requireNpmFromCwd(
3
+ 'react-dev-utils/formatWebpackMessages'
4
+ );
5
+ const chalk = requireNpmFromCwd('react-dev-utils/chalk');
6
+ const printBuildError = requireNpmFromCwd('react-dev-utils/printBuildError');
7
+ const webpack = requireNpmFromCwd('webpack');
8
+
9
+ // TODO:展示进度条
10
+ // TODO: 展示结果
11
+ module.exports = function runWebpack(config, webpackEnv = 'development') {
12
+ const isDev = webpackEnv === 'development';
13
+ isDev ? start(config) : build(config);
14
+ };
15
+ function start(config) {
16
+ const compiler = webpack(config);
17
+ compiler.watch({}, (err, stats) => {
18
+ if (err) {
19
+ return printBuildError(err);
20
+ }
21
+ const messages = formatWebpackMessages(
22
+ stats.toJson({ all: false, warnings: true, errors: true })
23
+ );
24
+ if (messages.errors.length) {
25
+ return printBuildError(new Error(messages.errors.join('\n\n')));
26
+ }
27
+ if (messages.warnings.length) {
28
+ console.log(chalk.yellow(messages.warnings.join('\n\n')));
29
+ }
30
+ console.log(chalk.green('SSR Compiled successfully.'));
31
+ });
32
+ }
33
+
34
+ function build(config) {
35
+ const compiler = webpack(config);
36
+ return new Promise((resolve, reject) => {
37
+ console.log('SSR Compiling...');
38
+ compiler.run((err, stats) => {
39
+ let messages;
40
+ if (err) {
41
+ if (!err.message) {
42
+ return reject(err);
43
+ }
44
+
45
+ let errMessage = err.message;
46
+
47
+ // Add additional information for postcss errors
48
+ if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) {
49
+ errMessage +=
50
+ '\nCompileError: Begins at CSS selector ' +
51
+ err['postcssNode'].selector;
52
+ }
53
+
54
+ messages = formatWebpackMessages({
55
+ errors: [errMessage],
56
+ warnings: [],
57
+ });
58
+ } else {
59
+ messages = formatWebpackMessages(
60
+ stats.toJson({
61
+ all: false,
62
+ warnings: true,
63
+ errors: true,
64
+ })
65
+ );
66
+ }
67
+ if (messages.errors.length) {
68
+ // Only keep the first error. Others are often indicative
69
+ // of the same problem, but confuse the reader with noise.
70
+ if (messages.errors.length > 1) {
71
+ messages.errors.length = 1;
72
+ }
73
+ return reject(new Error(messages.errors.join('\n\n')));
74
+ }
75
+ if (
76
+ process.env.CI &&
77
+ (typeof process.env.CI !== 'string' ||
78
+ process.env.CI.toLowerCase() !== 'false') &&
79
+ messages.warnings.length
80
+ ) {
81
+ console.log(
82
+ chalk.yellow(
83
+ '\nTreating warnings as errors because process.env.CI = true.\n' +
84
+ 'Most CI servers set it automatically.\n'
85
+ )
86
+ );
87
+ return reject(new Error(messages.warnings.join('\n\n')));
88
+ }
89
+
90
+ const resolveArgs = {
91
+ stats,
92
+ previousFileSizes: 0,
93
+ warnings: messages.warnings,
94
+ };
95
+ return resolve(resolveArgs);
96
+ });
97
+ })
98
+ .then(
99
+ ({ warnings }) => {
100
+ if (warnings.length) {
101
+ console.log(chalk.yellow('Compiled with warnings.\n'));
102
+ console.log(warnings.join('\n\n'));
103
+ console.log(
104
+ '\nSearch for the ' +
105
+ chalk.underline(chalk.yellow('keywords')) +
106
+ ' to learn more about each warning.'
107
+ );
108
+ console.log(
109
+ 'To ignore, add ' +
110
+ chalk.cyan('// eslint-disable-next-line') +
111
+ ' to the line before.\n'
112
+ );
113
+ } else {
114
+ console.log(chalk.green('Compiled successfully.\n'));
115
+ }
116
+ },
117
+ (err) => {
118
+ const tscCompileOnError =
119
+ process.env.TSC_COMPILE_ON_ERROR === 'true';
120
+ if (tscCompileOnError) {
121
+ console.log(
122
+ chalk.yellow(
123
+ 'Compiled with the following type errors (you may want to check these before deploying your app):\n'
124
+ )
125
+ );
126
+ printBuildError(err);
127
+ } else {
128
+ console.log(chalk.red('Failed to compile.\n'));
129
+ printBuildError(err);
130
+ process.exit(1);
131
+ }
132
+ }
133
+ )
134
+ .catch((err) => {
135
+ if (err && err.message) {
136
+ console.log(err.message);
137
+ }
138
+ process.exit(1);
139
+ });
140
+ }