搜索
您的当前位置:首页正文

create-react-app 修改为多入口编译的方法

2020-11-27 来源:抵帆知识网

需求和出发点

我们会有较多的小的单页应用,主要是一些简单的页面和活动之类。这些页面相互之间没有交集,但是会有一些可以共用的代码,资源、接口、组件啥的。

对此,我们想到了两种解决方案:

  • react-router 路由方案;
  • 同一个项目的多入口编译;
  • 针对我们的业务需求,其实 react-router 方案会有两个小问题:

  • 单个活动的修改,其实需要编译整个项目;
  • 若是不做编译优化,整个项目的包会比较大,但其实没必要,当然这个可以通过 react-router 的按需加载来解决;
  • 权衡之下,我们还是选择了第二个方案——改造项目成为多入口编译。

    文件结构设计

    改进后,整个项目的结构大体如下:

    - project
     - build
     - config
     - public
     - scripts
     - src
     - api
     - component
     - site
     - site1
     - index.html
     - index.js
     - ...
     - site2
     - index.html
     - index.js
     - ...
     - package.json
    

    site 文件夹下的所有文件夹都是一个独立的项目,项目通用的代码、资源被抽离到更外层的文件夹内,如 api、component 等,文件夹内都会有自己的 index.html 和 index.js,这会作为该项目的 html 模板和入口文件。下面,我们看下是如何修改编译过程的。

    修改入口和出口

    编译需要指定编译的入口和输出的位置,在 create-react-app 本来生成的 code 中,只有单入口和单出口,但是其实 webpack 是支持多入口、多出口的。

    入口修改

    create-react-app 命令生成的 config 文件夹中,有个 paths.js 文件,这里面 export 了比较常用的路径。在这里,我对 src/site 文件夹内的文件夹进行了遍历,生成为对象。具体代码如下:

    // all site paths
    function allSitePath(source) {
     const { lstatSync, readdirSync } = fs
     const { join } = path
     const result = {}
     const isDirectory = source => lstatSync(source).isDirectory()
     readdirSync(source).map(name => {
     let path = join(resolveApp(source), name)
     if (isDirectory(path)) result[name] = path
     })
     return result
    }
    
    module.exports = {
     ...
     allSites: allSitePath('src/site'),
    }
    
    

    在 webpack.config.dev.js / webpack.config.prod.js 中找到 module.exports 的 entry 属性,将其修改为:

    // 动态生成 entry
    const entry = {}
    Object.keys(paths.allSites).forEach(item => {
     entry[item] = [
     require.resolve('./polyfills'),
     require.resolve('react-dev-utils/webpackHotDevClient'),
     require.resolve('react-error-overlay'),
     paths.allSites[item]
     ]
    })
    
    module.exports = {
     ...
     entry: entry,
     ...
    }
    
    

    出口修改

    出口的修改分为两部分,一部分是 module.exports 的 output,添加 name 以使静态资源区分不同项目:

    module.exports = {
     ...
     output: {
     path: paths.appBuild,
     pathinfo: true,
     filename: 'static/js/[name].bundle.js',
     chunkFilename: 'static/js/[name].chunk.js',
     publicPath: publicPath,
     devtoolModuleFilenameTemplate: info =>
     path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
     },
     ...
    } 
    

    另一部分是 plugin 的修改,webpack 中,每个 HTML 文件的输出,其实是一个 HtmlWebpackPlugin,我们需要添加多个 HtmlWebpackPlugin,以求生成多个 HTML:

    // 动态生成 plugins
    const plugins = []
    Object.keys(paths.allSites).forEach(item => {
     plugins.push(new HtmlWebpackPlugin({
     inject: true,
     chunks: [item],
     template: `${paths.allSites[item]}/index.html`,
     filename: `${item}/index.html`,
     }))
    })
    
    module.exports = {
     ...
     plugins: [
     ...
     ].concat(plugins), 
     ...
    }
    
    

    修改 webpack Dev Server 配置

    上述配置做完后,理论就可以打包出多入口的版本;但使用npm start启动后,发现无论输入/index.html还是/admin.html,好像都是和原来/index.html显示一样的内容。甚至输入显然不存在的/xxxx.html,也显示为/index.html的内容。

    这里,我们还需要修改 /config/webpackDevServer.config.js,做一些额外配置。

    const rewrites = []
    Object.keys(paths.allSites).forEach(item => {
     rewrites.push({
     from: new RegExp(`^\\/${item}/`, 'i'),
     to: `/${item}/index.html`,
     })
    })
    
    ...
    
    module.exports = function(proxy, allowedHost) {
     return {
     ...
     historyApiFallback: {
     // Paths with dots should still use the history fallback.
     // See https://github.com/facebookincubator/create-react-app/issues/387.
     disableDotRule: true,
     // 指明哪些路径映射到哪个html
     rewrites: rewrites,
     },
     ...
     };
    };
    
    

    OK,到这里,整个改造就完成了。

    Top