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,
}),
],
};

相关文章
|
18天前
|
前端开发 JavaScript
学习react基础(3)_setState、state、jsx、使用ref的几种形式
本文探讨了React中this.setState和this.state的区别,以及React的核心概念,包括核心库的使用、JSX语法、类与函数组件的区别、事件处理和ref的使用。
37 3
学习react基础(3)_setState、state、jsx、使用ref的几种形式
|
5天前
|
JavaScript 前端开发 开发者
VUE 开发——Node.js学习(一)
VUE 开发——Node.js学习(一)
28 3
|
18天前
|
前端开发
学习react基础(2)_props、state和style的使用
本文介绍了React中组件间数据传递的方式,包括props和state的使用,以及如何在React组件中使用style样式。
24 0
|
14天前
|
算法 JavaScript 前端开发
第一个算法项目 | JS实现并查集迷宫算法Demo学习
本文是关于使用JavaScript实现并查集迷宫算法的中国象棋demo的学习记录,包括项目运行方法、知识点梳理、代码赏析以及相关CSS样式表文件的介绍。
第一个算法项目 | JS实现并查集迷宫算法Demo学习
|
15天前
|
JavaScript 前端开发 API
紧跟月影大佬的步伐,一起来学习如何写好JS(上)
该文章跟随月影老师的指导,探讨了编写优质JavaScript代码的三大原则:各司其职、组件封装与过程抽象,通过具体示例讲解了如何在实际开发中应用这些原则以提高代码质量和可维护性。
紧跟月影大佬的步伐,一起来学习如何写好JS(上)
|
18天前
vite.config.js中vite.defineConfig is not defined以及创建最新版本的vite项目
本文讨论了在配置Vite项目时遇到的`vite.defineConfig is not defined`错误,这通常是由于缺少必要的导入语句导致的。文章还涉及了如何创建最新版本的Vite项目以及如何处理`configEnv is not defined`的问题。
44 3
vite.config.js中vite.defineConfig is not defined以及创建最新版本的vite项目
|
7天前
|
开发框架 前端开发 JavaScript
React、Vue.js 和 Angular主流前端框架和选择指南
在当今的前端开发领域,选择合适的框架对于项目的成功至关重要。本文将介绍几个主流的前端框架——React、Vue.js 和 Angular,探讨它们各自的特点、开发场景、优缺点,并提供选择框架的建议。
25 6
|
13天前
|
JavaScript 前端开发 Oracle
软件工程师,学习下JavaScript ES6新特性吧
软件工程师,学习下JavaScript ES6新特性吧
35 9
|
17天前
|
前端开发 JavaScript API
React、Vue.js 和 Angular前端三大框架对比与选择
前端框架是用于构建用户界面的工具和库,它提供组件化结构、数据绑定、路由管理和状态管理等功能,帮助开发者高效地创建和维护 web 应用的前端部分。常见的前端框架如 React、Vue.js 和 Angular,能够提高开发效率并促进团队协作。
36 4
|
15天前
|
JavaScript 前端开发 算法
紧跟月影大佬的步伐,一起来学习如何写好JS(下)
该文章延续了上篇的内容,进一步探讨了编写优秀JavaScript代码的实践,强调了代码风格一致性、性能优化、团队约定的重要性,并通过实际案例分析了如何在不同场景下写出合适的代码。