1

CSS 文件将如何在客户端使用 react-loadable 库动态加载?

我在服务器端和客户端渲染中都包含了 react-loadable 库,从服务器端渲染一切正常,但客户端,CSS 将如何动态加载?

webpack.config.prod.js客户端/服务器 -

'use strict';

const autoprefixer = require('autoprefixer');
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const paths = require('./paths');
const getClientEnvironment = require('./env');
const { ReactLoadablePlugin } = require('react-loadable/webpack');

const publicPath = paths.servedPath;

const shouldUseRelativeAssetPaths = publicPath === './';

const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
const publicUrl = publicPath.slice(0, -1);
const env = getClientEnvironment(publicUrl);
if (env.stringified['process.env'].NODE_ENV !== '"production"') {
  throw new Error('Production builds must have NODE_ENV=production.');
}
const cssFilename = 'static/css/[name].[contenthash:8].css';

const client = {
  bail: true,
  devtool: shouldUseSourceMap ? 'source-map' : false,
  entry: [require.resolve('./polyfills'), paths.appIndexJs],
  output: {
    // The build folder.
    path: paths.appBuild,
    filename: 'static/js/[name].[chunkhash:8].js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
    publicPath,
    devtoolModuleFilenameTemplate: info =>
      path
        .relative(paths.appSrc, info.absoluteResourcePath)
        .replace(/\\/g, '/'),
  },
  resolve: {
    modules: ['node_modules', paths.appNodeModules].concat(
  process.env.NODE_PATH.split(path.delimiter).filter(Boolean)),
    extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
    alias: {
      'react-native': 'react-native-web',
    },
    plugins: [
      new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),

    ],
  },
  module: {
    strictExportPresence: true,
    rules: [
      {
        test: /\.(js|jsx|mjs)$/,
        enforce: 'pre',
        use: [
          {
            options: {
              formatter: eslintFormatter,
              eslintPath: require.resolve('eslint'),

            },
            loader: require.resolve('eslint-loader'),
          },
        ],
        include: paths.appSrc,
      },
      {
        oneOf: [
   
          {
            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
            loader: require.resolve('url-loader'),
            options: {
              limit: 10000,
              name: 'static/media/[name].[hash:8].[ext]',
            },
          },
          // Process JS with Babel.
          {
            test: /\.(js|jsx|mjs)$/,
            include: paths.appSrc,
            loader: require.resolve('babel-loader'),
            options: {
              compact: true,
              plugins: ['react-loadable/babel'],
            },
          },
          {
            test: /\.(?:css|less)$/,
            use: ExtractCssChunks.extract({
              use: [
                {
                  loader: 'css-loader?modules',
                  options: {
                    minimize: true,
                    sourceMap: shouldUseSourceMap,
                    importLoaders: true,
                    localIdentName: '[name]__[local]__[hash:base64:7]',
                  },
                },
                {
                  loader: 'less-loader',
                  options: {
                    minimize: true,
                    sourceMap: shouldUseSourceMap,
                    importLoaders: true,
                  },
                },
                {
                  loader: require.resolve('postcss-loader'),
                  options: {
                  ident: 'postcss',
                    plugins: () => [
                      require('postcss-flexbugs-fixes'),
                      autoprefixer({
                        browsers: [
                          '>1%',
                          'last 4 versions',
                          'Firefox ESR',
                          'not ie < 9',
                        ],
                        flexbox: 'no-2009',
                      }),
                    ],
                  },
                },
              ],
              fallback: 'style-loader',
            }),
            exclude: /\.(eot|woff|woff2|ttf|otf|svg)(\?[\s\S]+)?$/,
          },
          {
            loader: require.resolve('file-loader'),
            exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
            options: {
              name: 'static/media/[name].[hash:8].[ext]',
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new InterpolateHtmlPlugin(env.raw),
    new HtmlWebpackPlugin({
      inject: true,
      template: paths.appHtml,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: false,
        minifyCSS: true,
        minifyURLs: true,
      },
    }),
    new webpack.DefinePlugin(env.stringified),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
        comparisons: false,
      },
      mangle: {
        safari10: true,
      },
      output: {
        comments: false,
        ascii_only: true,
      },
      sourceMap: shouldUseSourceMap,
    }),
    new ExtractCssChunks({
      filename: cssFilename,
    }),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest.js',
      minChunks: Infinity,
    }),
    new ManifestPlugin({
      fileName: 'asset-manifest.json',
    }),
    new ReactLoadablePlugin({
      filename: './build/react-loadable.json',
    }),
    new SWPrecacheWebpackPlugin({
      dontCacheBustUrlsMatching: /\.\w{8}\./,
      filename: 'service-worker.js',
      logger(message) {
        if (message.indexOf('Total precache size is') === 0) {
          return;
        }
        if (message.indexOf('Skipping static resource') === 0) {
          return;
        }
        console.log(message);
      },
      minify: true,
      navigateFallback: `${publicUrl}/index.html`,
      navigateFallbackWhitelist: [/^(?!\/__).*/],
      staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    }),
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
  ],

  node: {
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty',
  },
};

// Server render
const nodeExternals = require('webpack-node-externals');
const server = Object.assign({}, client);
server.target = 'node';
server.node = {
  __filename: true,
  __dirname: true,
};
server.externals = [nodeExternals()];
server.entry = [
  './server/middleware/renderer.js',
];

delete server.devtool;
delete server.node;
server.module = {};
server.plugins = [
  new webpack.HashedModuleIdsPlugin(),
  new webpack.optimize.LimitChunkCountPlugin({
    maxChunks: 1,
  }),
];
server.output = {
  path: paths.appBuild,
  filename: 'handleRender.js',
  publicPath,
  libraryTarget: 'commonjs2',
};
server.module.rules = [{
  test: /\.(?:js|jsx)$/,
  exclude: /node_modules/,
  loader: require.resolve('babel-loader'),
  options: {
    compact: true,
    plugins: ['react-loadable/babel'],
  },
},
{
  test: /\.(?:css|less)$/,
  loader: 'css-loader/locals?modules&localIdentName=[name]__[local]__[hash:base64:7]!less-loader',
  exclude: /\.(eot|woff|woff2|ttf|otf|svg)(\?[\s\S]+)?$/,
}];

module.exports = [server, client];

Server index.js

...
import Loadable from 'react-loadable';
import serverRenderer from '../build/handleRender.js';
...
router.use('*', serverRenderer);
...
app.use(router);
// Pre-load all compoenents
Loadable.preloadAll().then(() => {
  app.listen(PORT, (error) => {
    if (error) {
      return console.log('something bad happened', error);
    }
    console.log(`listening on ${PORT}...`);
  });
}).catch((e) => {
  console.log('Loadable Error : ', e);
});

renderer.js

import { renderToStringWithData } from 'react-apollo';
import Loadable from 'react-loadable';
import { getBundles } from 'react-loadable/webpack';
...

const mainApp = renderToStringWithData(<Loadable.Capture
      report={moduleName => modules.push(moduleName)}
    >
      <App req={req} context={context} client={client} />
    </Loadable.Capture>);
...
  const bundles = getBundles(JSON.parse(stats), modules);
  const styles = bundles.filter(bundle =>                   bundle.file.endsWith('.css'));
  const scripts = bundles.filter(bundle => bundle.file.endsWith('.js'));    

...
//mainApp=>html
   const replacedStyle = html.replace(
            '<link id="codeSplittingStyle">',
            styles.map(bundle => `<link
                rel="stylesheet"
                href="/${bundle.file}"/>`).join('\n'),
          );

          const replacedScript = replacedStyle.replace(
            '<script id="codeSplittingScript"></script>',
            scripts.map(bundle => `<script
                type="text/javascript"
                src="/${bundle.file}"></script>`).join('\n'),
          );
...
   return res.send(replacedScript);    

Browser.js

import React from 'react';
import ReactDOM from 'react-dom';
import Loadable from 'react-loadable';

import Browser from './layout/browser';
import registerServiceWorker from './registerServiceWorker';

Loadable.preloadReady().then(() => {
  ReactDOM.hydrate(<Browser />, document.getElementById('root'));
});
registerServiceWorker();

4

1 回答 1

1

请查看extract-css-chunks-webpack-pluginrepo ( https://github.com/faceyspacey/extract-css-chunks-webpack-plugin#desired-output ) 中的“Desired output”部分。它指出:

webpack-flush-chunks 将收集确切的样式表以嵌入到您的响应中。它基本上可以自动生成上述内容。

因此,您需要使用它webpack-flush-chunks来生成cssHash动态 css 加载的重要部分,因为它决定了何时加载 css 块。

于 2018-04-26T09:06:01.253 回答