server-renderer
Version:
library of server side render for React
273 lines (272 loc) • 10.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const fs_1 = tslib_1.__importDefault(require("fs"));
const webpack_1 = tslib_1.__importDefault(require("webpack"));
const mini_css_extract_plugin_1 = tslib_1.__importDefault(require("mini-css-extract-plugin"));
const html_webpack_plugin_1 = tslib_1.__importDefault(require("html-webpack-plugin"));
const terser_webpack_plugin_1 = tslib_1.__importDefault(require("terser-webpack-plugin"));
const optimize_css_assets_webpack_plugin_1 = tslib_1.__importDefault(require("optimize-css-assets-webpack-plugin"));
// @ts-ignore
const html_webpack_inline_source_plugin_1 = tslib_1.__importDefault(require("html-webpack-inline-source-plugin"));
// @ts-ignore
const postcss_safe_parser_1 = tslib_1.__importDefault(require("postcss-safe-parser"));
const webpack_node_externals_1 = tslib_1.__importDefault(require("webpack-node-externals"));
const webpack_merge_1 = tslib_1.__importDefault(require("webpack-merge"));
const ora_1 = tslib_1.__importDefault(require("ora"));
const config_1 = require("./config");
function createWebpackConfig(isServer) {
const config = config_1.getConfig();
const resolveAppPath = (p) => path_1.default.resolve(config.rootDir, p);
const plugins = getPlugins(isServer, config);
const optimization = getOptimization(isServer, config);
const webpackConfig = {
bail: true,
mode: config.isDev ? 'development' : 'production',
// only client and in development mode
devtool: (config.isDev && !isServer) ? 'cheap-module-eval-source-map' : false,
stats: 'errors-warnings',
target: isServer ? 'node' : 'web',
entry: {
app: isServer ? config.serverEntry : resolveAppPath('src/index.tsx')
},
output: {
path: path_1.default.join(config.distDir, isServer ? 'server' : 'client'),
publicPath: isServer ? '/' : config.publicPath,
filename: (!isServer && !config.isDev) ? 'js/[name].[chunkhash:5].js' : '[name].js',
chunkFilename: (!isServer && !config.isDev) ? 'js/[name].[chunkhash:5].chunk.js' : '[name].chunk.js',
libraryTarget: isServer ? 'commonjs' : 'umd',
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
alias: {
'server-renderer': isServer
? 'server-renderer/lib/server.js'
: 'server-renderer/lib/client.js',
src: resolveAppPath('src'),
},
},
module: {
rules: [
{
test: /\.(t|j)sx?$/,
include: resolveAppPath('src'),
loader: require.resolve('babel-loader'),
options: {
plugins: [
require.resolve('@babel/plugin-proposal-class-properties'),
[
require.resolve('@babel/plugin-transform-runtime'),
{
helpers: true,
regenerator: true,
useESModules: false
}
]
],
presets: [
require.resolve('@babel/preset-react'),
require.resolve('@babel/preset-env'),
[
require.resolve('@babel/preset-typescript'),
{
isTSX: true,
allExtensions: true,
allowNamespaces: false,
}
],
],
}
},
{
test: /\.s?css$/,
oneOf: [
{
test: (modulePath) => {
const basename = path_1.default.basename(modulePath);
// 大写开头的就当做 css module
return /^[A-Z]/.test(basename);
},
use: getStyleLoaders(isServer, true, config),
},
{
use: getStyleLoaders(isServer, false, config),
}
],
},
]
},
externals: isServer ? [webpack_node_externals_1.default()] : undefined,
plugins,
optimization,
node: {
__dirname: isServer,
__filename: isServer,
}
};
return mergeConfig(webpackConfig, isServer, config);
}
exports.createWebpackConfig = createWebpackConfig;
function mergeConfig(webpackConfig, isServer, config) {
if (config.configureWebpack) {
if (typeof config.configureWebpack === 'function') {
return config.configureWebpack(webpackConfig, isServer, config);
}
return webpack_merge_1.default(webpackConfig, config.configureWebpack);
}
return webpackConfig;
}
function getProgressPlugin() {
const spinner = ora_1.default();
return new webpack_1.default.ProgressPlugin((percentage, msg) => {
if (!spinner.isSpinning) {
spinner.start();
}
spinner.text = `${(percentage * 100).toFixed(2)}% ${msg}`;
if (percentage === 1) {
spinner.stop();
}
});
}
function getPlugins(isServer, config) {
const envVariables = getEnvVariables(config);
const plugins = [
new webpack_1.default.DefinePlugin(envVariables)
];
if (isServer || !config.isDev) {
plugins.push(getProgressPlugin());
}
if (!isServer && !config.isDev) {
plugins.push(new mini_css_extract_plugin_1.default({
filename: 'style/[name].[contenthash:5].css',
chunkFilename: 'style/[name].[contenthash:5].css',
}), new html_webpack_plugin_1.default({
template: config.htmlTemplatePath,
filename: config.builtHTMLPath,
// 将 runtime.[hash].js 内联引入
inlineSource: /runtime.(\w)*\.js$/,
}), new html_webpack_inline_source_plugin_1.default());
}
return plugins;
}
function getOptimization(isServer, config) {
if (isServer || config.isDev) {
return undefined;
}
const useSourceMap = !isServer && !config.isDev;
return {
minimizer: [
new terser_webpack_plugin_1.default({
extractComments: false,
terserOptions: {
sourceMap: useSourceMap,
safari10: true,
output: {
comments: false,
}
}
}),
new optimize_css_assets_webpack_plugin_1.default({
cssProcessorOptions: {
parser: postcss_safe_parser_1.default,
map: {
inline: false,
annotation: true,
},
},
}),
],
splitChunks: {
chunks: 'all',
name: true,
cacheGroups: {
vonders: {
test: /[\\/]node_modules[\\/]/,
name: 'vonders',
priority: 10,
},
},
},
runtimeChunk: {
name: 'runtime',
},
};
}
function getStyleLoaders(isServer, isModule, config) {
const useSourceMap = !isServer && !config.isDev;
const cssLoader = {
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: isModule && {
localIdentName: '[name]-[local]',
},
// .hello-world 转成 classes.helloWorld
localsConvention: 'camelCaseOnly',
sourceMap: useSourceMap,
},
};
const styleLoader = isServer
? require.resolve('isomorphic-style-loader')
: config.isDev
? require.resolve('style-loader')
: mini_css_extract_plugin_1.default.loader;
const loaders = [
styleLoader,
cssLoader,
];
if (!isServer && !config.isDev) {
loaders.push({
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
],
sourceMap: useSourceMap,
}
});
}
loaders.push({
loader: require.resolve('sass-loader'),
options: {
data: config.sassData,
sourceMap: useSourceMap,
}
});
return loaders;
}
function getEnvVariables(config) {
const filename = `.env.${process.env.NODE_ENV}.local`;
const fullPath = path_1.default.join(config.rootDir, filename);
if (fs_1.default.existsSync(fullPath)) {
const fileContent = fs_1.default.readFileSync(fullPath, 'utf-8');
fileContent.split('\n').forEach(line => {
if (!line.includes('=')) {
return;
}
const [key, value] = line.split('=');
process.env[key] = value;
});
}
const variables = Object.keys(process.env)
.reduce((variables, envKey) => {
if (envKey.includes('APP_')) {
variables[envKey] = process.env[envKey];
}
return variables;
}, { NODE_ENV: process.env.NODE_ENV, PORT: config.port });
return {
'process.env': Object.keys(variables).reduce((env, key) => {
env[key] = JSON.stringify(variables[key]);
return env;
}, {})
};
}