使用TypeScript和NO Babel拆分React组件的代码

2020年12月30日11:05:50 发表评论 29 次浏览

本文概述

网络性能的秘诀在于更少的代码

随着现在臭名昭著的单页面应用程序的出现, 大量JavaScript开始被推向浏览器。 JavaScript的绝对重量是一个问题, 但是浏览器还必须解析下载的JavaScript。当浏览器的UI线程超出其预期的用途时, 它可能会处于这种压力之下。显而易见的答案是减少代码。代码拆分使我们能够做到这一点而无需交付更少的功能。

代码拆分是一项复杂的业务, 将一捆代码拆分为较小的块, 可以按需加载。幸运的是, 像webpack这样的工具将这种复杂性抽象到了一个不太复杂的API之后。不幸的是, 这种不太复杂的API仍然非常复杂。在React生态系统中, 诸如可加载的组件在周围添加更简单的理智贴面动态进口.

按路线拆分代码

我希望看到更多的渲染控件返回到服务器。浏览器并非旨在呈现HTML, 并且有很多充分的理由使呈现React服务器端更为可取。我预计我们将看到更多的HTML呈现在服务器端。

下面是一些代码我的公司使用动态导入创建较小代码文件的网站, 这些文件可以按需加载。

import React from 'react';
import loadable from '@loadable/component';
import * as Urls from '../urls';
import { RouteProps, Route, Switch } from 'react-router';

export type Page<P = unknown> = RouteProps & {
  heading: string;
  path: string;
  footerPage?: boolean;
} & P;

const fallback = <div>loading....</div>;

const Home = loadable(() => import('src/components/Home'), {
  fallback, });
const OSS = loadable(() => import('src/components/OSS'), {
  fallback: <div>Loading...</div>, });
const Blog = loadable(() => import('src/components/Blog'), {
  fallback: <div>Loading...</div>, });

export const routable: Page[] = [
  {
    heading: 'Home', path: Urls.Home, component: Home, exact: true, }, {
    heading: 'OSS', path: Urls.OSS, component: OSS, exact: true, }, // etc.

的可装载函数将动态导入作为参数, 并将为我们完成艰苦的工作。运行webpack构建会创建几个可以延迟加载的较小文件:

已进行代码拆分的文件列表

@ loadable / babel-plugin

我是TypeScript的忠实拥护者, 而且我一直都远离需要Babel的事物, 因为必须维护两个不同的Transpiler配置不是我愿意走的路。

的@ loadable / babel-plugin转换如下代码:

import loadable from '@loadable/component';

export const LazyFoo = loadable(() => import('./input/AsyncDefaultComponent'));

变成这样的代码:

import loadable from 'loadable-components';

export const LazyFoo = loadable({
  chunkName() {
    return 'input-AsyncDefaultComponent';
  }, isReady(props) {
    return (
      typeof __webpack_modules__ !== 'undefined' &&
      Boolean(__webpack_modules__[this.resolve(props)])
    );
  }, requireAsync: () =>
    import(
      /* "webpackChunkName":"input-AsyncDefaultComponent" */ './input/AsyncDefaultComponent'
    ), requireSync(props) {
    return typeof '__webpack_require__' !== 'undefined'
      ? __webpack_require__(this.resolve(props))
      : eval('module.require')(this.resolve(props));
  }, resolve() {
    if (require.resolveWeak)
      return require.resolveWeak(
        /* "webpackChunkName":"input-AsyncDefaultComponent" */ './input/AsyncDefaultComponent', );
    else
      return eval('require.resolve')(
        /* "webpackChunkName":"input-AsyncDefaultComponent" */ './input/AsyncDefaultComponent', );
  }, });

可加载TS变压器

现在进入作品的英雄, 即可加载TS变压器它与Babel同行所做的工作相同, 只是通过创建一个TypeScript转换器。 TypeScript转换器使我们能够进入编译管道并转换代码, 就像上面Babel插件中列出的那样。完整的AST可供开发人员随意使用。

我们为制作了一个自定义演示.
不完全是。点击这里查看.

使用TypeScript和NO Babel拆分React组件的代码1

将loadable-ts-transformer连接到Webpack构建

第一步是定义要分割成较小块的组件, 可加载组件的可加载功能:

const Home = loadable(() => import('src/components/Home'), {
  fallback, });

接下来, 需要配置webpack。通常, 在webpack ssr(服务器端呈现)构建中, 你具有服务器webpack配置文件和客户端webpack配置文件。

Webpack服务器配置负责捆绑节点快递代码在服务器端呈现React组件。

为了减少两个配置文件之间的重复, 我使用webpack合并创建一个common.config.js合并到两个文件中的文件client.config.js和server.config.js文件。

以下是一个例子common.config.js该文件具有webpack客户端和服务器配置文件的通用组件:

const path = require("path");
const { loadableTransformer } = require('loadable-ts-transformer');

module.exports = {
  resolve: {
    extensions: ['.ts', '.tsx', '.js'], }, module: {
    rules: [
      {
        test: /\.tsx?$/, exclude: /node_modules/, loader: 'ts-loader', options: {
          transpileOnly: true, getCustomTransformers: () => ({ before: [loadableTransformer] }), }, }
    ], }, };

我用ts-loader将TypeScript转换成JavaScript并ts-loader有个getCustomTransformers我们可以用来添加可加载TS变压器.

的client.config.js文件看起来像这样:

const path = require("path");
const merge = require('webpack-merge');
const LoadablePlugin = require('@loadable/webpack-plugin');
const commonConfig = require('./webpack.config');
const webpack = require('webpack');

module.exports = () => {
  return merge(commonConfig, {
    output: {
      path: path.resolve(__dirname, 'public'), publicPath: '/assets/', filename: '[name].[chunkhash].js', }, entry: {
      main: path.resolve(__dirname, 'src/client.tsx'), }, optimization: {
      splitChunks: {
        name: 'vendor', chunks: 'initial', }, }, plugins: [
      new LoadablePlugin(), new webpack.DefinePlugin({ __isBrowser__: "true" })
    ], });
};

注意使用webpack.DefinePlugin添加一个__isBrowser__属性放入捆绑的代码中。这不再需要无休止地使用窗口类型==='未定义'检查以确定代码是否在服务器或浏览器上执行。

的client.config.js文件还添加了@ loadable / webpack-plugin到插件数组。请勿将此添加到server.config.js.

的server.config.js文件看起来像这样:

const path = require("path");
const merge = require('webpack-merge');
const commonConfig = require('./webpack.config');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');

module.exports = () => {
  return merge(commonConfig, {
    target: 'node', externals:  nodeExternals({
      whitelist: [
          /^@loadable\/component$/, /^react$/, /^react-dom$/, /^loadable-ts-transformer$/, ]
      }), ], output: {
      path: path.resolve(__dirname, 'dist-server'), filename: '[name].js', }, entry: {
      server: path.resolve(__dirname, 'src/server.tsx'), }, plugins: [
     new webpack.DefinePlugin({ __isBrowser__: "false" })
   ]
  });
};

的webpack外部部分使我绊倒了很多次。 externals属性使你可以将捆绑在Webpack服务器内部的内容列入白名单。你不想捆绑整个node_modules夹。我发现webpack-node-externals软件包其中有一个白名单选项非常有用。

服务器端可加载组件

的server.config.js文件定义和入口点ofsrc /服务器/index.ts看起来像这样:

export const app = express();
const rootDir = process.cwd();

const publicDir = path.join(rootDir, isProduction ? 'dist/public' : 'public');
app.use(express.static(publicDir));

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.get('/*', async (req, res) => {
  await render({
    req, res, });
});

上面代码的重点是:

  • 的app.use(express.static(publicDir));代码点表示webpack使用以下命令输出的静态文件表达静态功能
  • 包包app.get('/ *。async(req。res)=> {将点路由到可重用渲染接下来我会解释的功能

的渲染功能如下:

const statsFile = path.resolve(process.cwd(), 'dist/loadable-stats.json');

export async function render({ req, res }: RendererOptions): Promise<void> {
  const extractor = new ChunkExtractor({
    entrypoints: ['client'], statsFile, });

  const context: StaticRouterContext = {};

  const html = renderToString(
    extractor.collectChunks(
      <StaticRouter location={req.url} context={context}>
        <Routes />
      </StaticRouter>, ), );

  res.status(HttpStatusCode.Ok).send(`
    <!doctype html>
    <html lang="en">
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        ${extractor.getStyleTags()}   
      </head>
      <body>
        <div id="root">${html}</div>
        ${extractor.getScriptTags()}
      </body>
    </html>
`);
}

上面的代码利用了块提取器组件它在服务器端收集块, 然后创建可在输出的HTML中使用的脚本标记或脚本元素。

$ {extractor.getStyleTags()}将输出CSS链接标签, 并且$ {extractor.getScriptTags()}将输出JavaScript脚本标签。

运行构建时, @ loadable / webpack-plugin生成一个名为loadable-stats.json, 其中包含有关webpack中所有条目和块的信息。

一旦到位, 块提取器负责从此文件中查找你的条目。

的entryPoints数组的块提取器组件设置为['客户']哪个映射到客户Webpack的属性client.config.js文件:

entry: {
  client: path.join(process.cwd(), 'src/client.tsx'), }, 

客户补液

客户端配置文件的入口点现在是具有客户属性的对象。

的client.tsx文件列出如下:

import React from 'react';
import { hydrate } from 'react-dom';
import { loadableReady } from '@loadable/component';

import { App } from '../containers/App';

const bootstrap = (): void => {
  const root = document.getElementById('root');

  if (!root) {
    return;
  }

  hydrate(<App />, root);
};

loadableReady(() => bootstrap());

通常, 在为React服务器端渲染的代码补水时, 你将使用ReactDom的水合功能但是在可加载组件在上面的世界中, 可加载组件的loadableReady函数用于等待所有脚本异步加载以确保最佳性能。所有脚本都是并行加载的, 因此你必须等待它们准备就绪, 使用loadableReady.

结语

由于需要Babel, 我避免使用许多代码拆分包。的可加载TS变压器已经治愈了。

如果你希望将其添加到可加载组件的来源然后请鸣响这个问题我发现了它的存在。

全面了解生产React应用

调试React应用程序可能很困难, 尤其是当用户遇到难以重现的问题时。如果你有兴趣监视和跟踪Redux状态, 自动显示JavaScript错误以及跟踪缓慢的网络请求和组件加载时间,

尝试notlogy

.

使用TypeScript和NO Babel拆分React组件的代码2
LogRocket仪表板免费试用横幅

日志火箭就像Web应用程序的DVR, 实际上记录了React应用程序中发生的一切。你可以汇总并报告问题发生时应用程序所处的状态, 而不用猜测为什么会发生问题。 notlogy还监视你的应用程序的性能, 并使用客户端CPU负载, 客户端内存使用情况等指标进行报告。

notlogy Redux中间件软件包在你的用户会话中增加了一层可见性。 notlogy记录Redux存储中的所有操作和状态。

现代化如何调试React应用程序-免费开始监控.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: