13.JS学习篇-ES6 React 项目模板

简介: 【8月更文挑战第25天】

1.项目能力支持
1.项目初始化脚手架
1.前端编码规范工程化(lint工具、Node CLI等)

2.用工具提升项目的编码规范,如:eslint、stylelint、commitlint、markdownlint、husky等

3.工具对于JavaScript、Typescript、React、Vue等不同类型的前端项目下的标准的语法限制;

2.相关基础功能
• React : 前端页面展示框架;
• Redux :状态管理;
• React Router :前端路由;
• Connected React Router :支持将 Redux 与 React Router 进行绑定;
• Express 服务端;
• TypeScript 类型检查;
• Webpack 打包构建工具;
• Babel : ES6+ 转 ES5 工具;
• nodemon :监测 Node 应用变化时,自动重启服务器;
• axios 基于 Promise 的 HTTP 客户端;
• react-helmet :在客户端及服务端上管理标题、 meta 、样式和脚本标签;
• loadable-component :支持组件的懒加载;
• Webpack Dev Middleware :通过 Express 服务器提供 webpack 服务;
• Webpack Hot Middleware :支持基于 Express 的热更新;
• Webpack Bundle Analyzer :打包分析工具;
• morgan :服务器日志;
• terser-webpack-plugin :压缩 JS ;
• css-minimizer-webpack-plugin :压缩 CSS ;
3.运行指令
使用cross-env提供跨平台的设置及环境变量:

step1

====================================安装初始脚手架

命令行 start

sudo npm install -g encode-fe-lint

sudo encode-fe-lint init

React 项目 (TypeScript)

Y Y Y

====================================命令行 end

step2

基础环境配置

  • 查看 package.json 文件

"private"

"script"-"preinstall"/"prepare"/"init"

"engines"

"devDependencies"

"dependencies"

====================================命令行 start

sudo npm install -g pnpm

sudo pnpm install

====================================命令行 end

2.项目初始化配置
项目目录:

  1. 新建 babel.config.js,以及内部配置

  2. tsconfig 的配置, tsconfig.json

sudo npm install -g typescript

sudo tsc --init

  1. 实现相关的 postcss,创建 postcss.config.js

  2. webpack cross-env

我们需要客户端和服务端渲染,所以如下

webpack 目录

  • base.config.ts

  • client.config.ts

  • server.config.ts

我们看到网上很多都是如下

  • webpack.common.js

  • webpack.dev.js

  • webpack.prod.js

webpack 是纯用于打包的,和 js/ts 没有关系的

webpack5 中 MiniCssExtractPlugin,将 css 分离出

progressPlugin 编译进度包

webpack-manifest-plugin ssr 中需要引入的,页面中的基本信息

loadablePlugin 分包的方式引入子包的内容

DefinePlugin 定义全局变量

bundle-analyze plugin 分析线上的包

⭐️⭐️⭐️⭐️⭐️⭐️⭐️

通用的能力做抽离,根据不同的环境,进行不同的配置。

先打包构建,生产产物

nodemon.json,开发过程中,可以监听 public 下面文件的变化&&服务端的更新

到这一步为止,基础环境已经配置完成.不用脚手架,自己手写也可以,加油吧~

3.客户端配置
1.入口文件
// src/app/index.tsx
const App = ({ route }: Route): JSX.Element => (




Logo


{config.APP.title}





{/ Child routes won't render without this /}
{renderRoutes(route.routes)}

};

// src/client/index.tsx
const render = (Routes: RouteConfig[]) =>
ReactDOM.hydrate(

{renderRoutes(Routes)}
,
document.getElementById('react-view'),
);

// loadable-component setup
loadableReady(() => render(routes as RouteConfig[]));

2.错误边界处理
import { ReactNode, PureComponent } from "react";

interface Props {
children?: ReactNode;
}
interface State {
error: Error | null;
errorInfo: { componentStack: string } | null;
}
class ErrorBoundary extends PureComponent {
constructor(props: Props) {
super(props);
this.state = { error: null, errorInfo: null };
}
componentDidCatch(error: Error, errorInfo: { componentStack: string }): void
// Catch errors in any components below and re-render with error message
this.setState({ error, errorInfo });
// You can also log error messages to an error reporting service here
}
render(): ReactNode {
const { children } = this.props;
const { errorInfo, error } = this.state;
// If there's an error, render error path
return errorInfo ? (


Something went wrong.



{error && error.toString()}


{errorInfo.componentStack}


) : (
children || null
);
}
}
export default ErrorBoundary;

3.页面入口配置
const Home: FC = (): JSX.Element => {
const dispatch = useDispatch();
const { readyStatus, items } = useSelector(
({ userList }: AppState) => userList,
shallowEqual
);
// Fetch client-side data here
useEffect(() => {
dispatch(fetchUserListIfNeed());
}, [dispatch]);
const renderList = () => {
if (!readyStatus || readyStatus === "invalid" || readyStatus === "request"
return

Loading...

;
if (readyStatus === "failure") return

Oops, Failed to load list!

;
return ;
};
return (


{renderList()}

);
};
// Fetch server-side data here
export const loadData = (): AppThunk[] => [
fetchUserListIfNeed(),
// More pre-fetched actions...
];
export default memo(Home);
1

4.路由配置
export default [
{
component: App,
routes: [
{
path: "/",
exact: true,
component: AsyncHome, // Add your page here
loadData: loadHomeData, // Add your pre-fetch method here
},
{
path: "/UserInfo/:id",
component: AsyncUserInfo,
loadData: loadUserInfoData,
},
{
component: NotFound,
},
],
},
] as RouteConfig[];

4.服务端配置
1.请求配置
// 使用https://jsonplaceholder.typicode.com提供的接口设置请求

export default {
HOST: 'localhost',
PORT: 3000,
API_URL: 'https://jsonplaceholder.typicode.com',
APP: {
htmlAttributes: { lang: 'zh' },
title: '萍宝贝 ES6 项目实战',
titleTemplate: '萍宝贝 ES6 项目实战 - %s',
meta: [
{
name: 'description',
content: 'wikiHong ES6 React 项目模板',
},
],
},
};

2.入口文件
const app = express();

// Use helmet to secure Express with various HTTP headers
app.use(helmet({ contentSecurityPolicy: false }));

// Prevent HTTP parameter pollution
app.use(hpp());

// Compress all requests
app.use(compression());

// Use for http request debug (show errors only)
app.use(logger('dev', { skip: (_, res) => res.statusCode < 400 }));
app.use(favicon(path.resolve(process.cwd(), 'public/logo.png')));
app.use(express.static(path.resolve(process.cwd(), 'public')));

// Enable dev-server in development
if (DEV) devServer(app);

// Use React server-side rendering middleware
app.get('*', ssr);

// @ts-expect-error
app.listen(config.PORT, config.HOST, (error) => {
if (error) console.error(chalk.red(==> 😭 OMG!!! ${error}));
});

3.html渲染
const html = <!doctype html> <html ${head.htmlAttributes.toString()}> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000" /> <link rel="icon" href="/logo.png" /> <link rel="apple-touch-icon" href="/logo192.png" /> <link rel="manifest" href="/manifest.json" /> ${head.title.toString()} ${head.base.toString()} ${head.meta.toString()} ${head.link.toString()} <!-- Insert bundled styles into <link> tag --> ${extractor.getLinkTags()} ${extractor.getStyleTags()} </head> <body> <!-- Insert the router, which passed from server-side --> <div id="react-view">${htmlContent}</div> <!-- Store the initial state into window --> <script> // Use serialize-javascript for mitigating XSS attacks. See the foll // http://redux.js.org/docs/recipes/ServerRendering.html#security-co window.__INITIAL_STATE__=${serialize(initialState)}; </script> <!-- Insert bundled scripts into <script> tag --> ${extractor.getScriptTags()} ${head.script.toString()} </body> </html>;

const minifyConfig = {
collapseWhitespace: true,
removeComments: true,
trimCustomFragments: true,
minifyCSS: true,
minifyJS: true,
minifyURLs: true,
};

// Minify HTML in production
return DEV ? html : minify(html, minifyConfig);

4.本地服务配置
export default (app: Express): void => {
const webpack = require("webpack");
const webpackConfig = require("../../webpack/client.config").default;
const compiler = webpack(webpackConfig);
const instance = require("webpack-dev-middleware")(compiler, {
headers: { "Access-Control-Allow-Origin": "" },
serverSideRender: true,
});
app.use(instance);
app.use(
require("webpack-hot-middleware")(compiler, {
log: false,
path: "/__webpack_hmr",
heartbeat: 10
1000,
})
);
instance.waitUntilValid(() => {
const url = http://${config.HOST}:${config.PORT};
console.info(chalk.green(==> 🌎 Listening at ${url}));
});
};

5.SSR配置
export default async (
req: Request,
res: Response,
next: NextFunction
): Promise => {
const { store } = createStore({ url: req.url });

// The method for loading data from server-side
const loadBranchData = (): Promise => {
const branch = matchRoutes(routes, req.path);
const promises = branch.map(({ route, match }) => {
if (route.loadData)
return Promise.all(
route
.loadData({
params: match.params,
getState: store.getState,
req,
res,
})
.map((item: Action) => store.dispatch(item))
);
return Promise.resolve(null);
});
return Promise.all(promises);
};

try {
// Load data from server-side first
await loadBranchData();
const statsFile = path.resolve(process.cwd(), "public/loadable-stats");
const extractor = new ChunkExtractor({ statsFile });
const staticContext: Record = {};
const App = extractor.collectChunks(

{/ Setup React-Router server-side rendering /}

{renderRoutes(routes)}


);
const initialState = store.getState();
const htmlContent = renderToString(App);
// head must be placed after "renderToString"
// see: https://github.com/nfl/react-helmet#server-usage
const head = Helmet.renderStatic();

// Check if the render result contains a redirect, if so we need to set
// the specific status and redirect header and end the response
if (staticContext.url) {
    res.status(301).setHeader("Location", staticContext.url);
    res.end();

    return;
}

// Pass the route and initial state into html template, the "statusCode" c
res
    .status(staticContext.statusCode === "404" ? 404 : 200)
    .send(renderHtml(head, extractor, htmlContent, initialState));

} catch (error) {
res.status(404).send("Not Found :(");
console.error(chalk.red(==> 😭 Rendering routes error: ${error}));
}
next();
};

5.构建工具处理
1.基础配置
const config = (isWeb: boolean):Configuration =>({
mode: isDev ? 'development' : 'production',
context: path.resolve(process.cwd()), // 上下文中的传递
// 压缩大小, 性能优化
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true // 保留console的内容
}
}
})
]
},
plugins: getPlugins(isWeb) as WebpackPluginInstance[],
module: {
// 解析对应的loader
rules: [
{
test: /.(t|j)sx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
caller: { target: isWeb ? 'web' : 'node' },
cacheDirectory: isDev,
},
},
{
test: /.css$/,
use: getStyleLoaders(isWeb),
},
{
test: /.(scss|sass)$/,
use: getStyleLoaders(isWeb, true),
},
{
test: /.(woff2?|eot|ttf|otf)$/i,
type: 'asset',
generator: { emit: isWeb },
},
{
test: /.(png|svg|jpe?g|gif)$/i,
type: 'asset',
generator: { emit: isWeb },
},
]
}
})

export default config;

getStyleLoaders 配置
// loader style-loader postcss-loader

const getStyleLoaders = (isWeb: boolean, isSaas?: boolean) => {
let loaders: RuleSetUseItem[] = [
{
loader: 'css-loader',
options: {
importLoaders: isSaas ? 2 : 1,
modules: {
auto: true,
exportOnlyLocals: !isWeb, // ssr
}
}
},
{
loader: 'postcss-loader',
}
];

if (isWeb) {
loaders = [
...loaders,
]
}

if (isSaas)
loaders = [
...loaders,
{
loader: 'sass-loader',
}
];

return loaders
}

getPlugins 配置
// plugins csr ssr
const getPlugins = (isWeb: boolean) => {
let plugins = [
new webpack.ProgressPlugin(),
// 适用于SSR服务下的manifest信息
new WebpackManifestPlugin({}), // 改变ssr返回页面的title
new LoadablePlugin({
writeToDisk: true,
filename: '../loadable-state.json', // 声明写入文件中的名称
}),
// 定义全局变量
new webpack.DefinePlugin({
CLIENT: isWeb,
SERVER: !isWeb,
DEV: isDev,
})
];

// 根据process,env NODE_ENV analyze
if (!isDev) {
plugins = [
...plugins,
new BundleAnalyzerPlugin({
analyzerMode: process.env.NODE_ENV === 'analyze' ? 'server' : 'disabled'
}),
];
}
return plugins
}

2.客户端配置
const config:Configuration = {
devtool: isDev && 'eval-cheap-source-map',
entry: './src/client',
output: {
filename: isDev ? '[name].js' : '[name].[contenthash].js',
chunkFilename: isDev ? '[id].js' : '[id].[contenthash].js',
path: path.resolve(process.cwd(), 'public/assets'),
publicPath: '/assets/',
},
optimization: {
minimizer: [new CssMinimizerPlugin()]
},
plugins: getPlugins()
}

export default merge(baseConfig(true), config)

getPlugins 配置

const getPlugins = () => {
let plugins = []

if (isDev) {
plugins = [
...plugins,
// 热更新
new webpack.HotModuleReplacementPlugin(),
// react refresh
new ReactRefreshWebpackPlugin(),
]
}
return plugins;
}
3.服务器端配置
const config: Configuration = {
target: 'node',
devtool: isDev ? 'inline-source-map' : 'source-map',
entry: './src/server',
output: {
filename: 'index.js',
chunkFilename: '[id].js',
path: path.resolve(process.cwd(), 'public/server'),
libraryTarget: 'commonjs2',
},
node: { dirname: true, filename: true },
externals: [
'@loadable/component',
nodeExternals({
// Load non-javascript files with extensions, presumably via loaders
allowlist: [/.(?!(?:jsx?|json)$).{1,5}$/i],
}),
] as Configuration['externals'],
plugins: [
// Adding source map support to node.js (for stack traces)
new webpack.BannerPlugin({
banner: 'require("source-map-support").install();', // 最新的更新时间
raw: true,
}),
],
};

相关文章
|
9天前
|
JavaScript
ES6学习(9)js中的new实现
ES6学习(9)js中的new实现
|
3天前
|
前端开发 JavaScript 算法
react学习(1)
react学习(1)
113 66
|
3天前
|
前端开发
react学习(2)
react学习(2)
122 57
|
3天前
|
JavaScript 前端开发
react学习(3)创建虚拟dom的两种方式
react学习(3)创建虚拟dom的两种方式
|
1天前
|
前端开发 JavaScript 开发者
Express.js与前端框架的集成:React、Vue和Angular的示例与技巧
本文介绍了如何将简洁灵活的Node.js后端框架Express.js与三大流行前端框架——React、Vue及Angular进行集成,以提升开发效率与代码可维护性。文中提供了详细的示例代码和实用技巧,展示了如何利用Express.js处理路由和静态文件服务,同时在React、Vue和Angular中构建用户界面,帮助开发者快速掌握前后端分离的开发方法,实现高效、灵活的Web应用构建。
23 3
|
2天前
|
JavaScript 前端开发 UED
让 HTML 向 Vue.js 华丽转身:如何把 `wangEditor` 仿腾讯文档项目整合进 Vue.js
让 HTML 向 Vue.js 华丽转身:如何把 `wangEditor` 仿腾讯文档项目整合进 Vue.js
|
1天前
|
前端开发 JavaScript
react学习(8)
react学习(8)
|
1天前
|
前端开发
react学习(7)
react学习(7)
|
2天前
|
前端开发
react学习(6)
react学习(6)
|
1天前
|
前端开发
react学习(9)
react学习(9)