参考 lerna 的方式拆分类单页应用

Posted 梅沙Fex

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了参考 lerna 的方式拆分类单页应用相关的知识,希望对你有一定的参考价值。

前言

在公司的全日制游戏项目中,原本的项目结构是这样的:

 
   
   
 
  1. .

  2. ├── README.md

  3. ├── build

  4. ├── config

  5. ├── package.json

  6. ├── src

  7.      ├── assets

  8.      ├── common

  9.      ├── components

  10.      ├── config

  11.      ├── filters

  12.      ├── lib

  13.      ├── pages

  14.            ├── 2bridge

  15.            ├── 2chase

  16.            ├── 3bridge

  17.            ├── 3chase

  18.            ├── 4chase

  19.            ├── 5chase

  20.            ├── bridge

  21.            ├── chase

  22.            ├── chaseOld

  23.            ├── coin

  24.            ├── dinosaur

  25.            ├── klgo

  26.            ├── machine

  27.            ├── minigame

  28.            ├── ramp

  29.            └── stage

  30.      ├── store

  31.      └── utils

  32. ├── static

  33. └── steamGame

src/pages/ 中,是每个独立的游戏项目,有独立的入口文件,但是部分通用的文件又在外部集合,包括静态资源。这种做法带来的坏处是不同的游戏之间耦合严重,开发和发布的时候构建项目的速度很慢(项目庞大),不能单独发布单个游戏,在开发的过程中新建游戏需要重复复制粘贴文件。导致开发的效率低下。最后决定参考 create-react-app 的做法,并且用类似 lerna 管理 npm 包的方式来控制单个游戏的打包。新的目录结构如下:

其中 create-steam-gamesteam-game-scripts 是参考 create-react-app 和 vue-cli 的做法。而单独打包更新游戏的命令集成在 steam-game-scripts 里面。 steam-game-assets 则是部分通用的库。每个游戏拥有独立的 package.json ,然后每个游戏在games 独立成一个文件夹,可以各自引入依赖,避免相互之间的影响,并且在开发过程中可以单独开发单个游戏。

create-steam-game

create-steam-game 基本参考 create-react-app 的做法。

全局安装后可以 通过 create-steam-game gameName 来创建新的游戏,会创建相应的游戏文件夹,并且安装 steam-game-scripts,然后调用 steam-game-scripts 中的 init.js,来安装相应的依赖和复制默认的文件,安装好的目录文件如下:

 
   
   
 
  1. .

  2. └── package.json

  3. └── src

  4.    ├── App.vue

  5.    ├── assets

  6.    ├── components

  7.    ├── config

  8.    ├── filters

  9.    ├── lang

  10.    ├── main.js

  11.    ├── router.js

  12.    ├── store.js

  13.    ├── utils

  14.    └── views

 
   
   
 
  1. function createSteamGame(name, packages) {

  2.  /**

  3.   1. 检查文件夹名是否规范

  4. */

  5.  const rootPath = path.resolve(name);

  6.  const gameName = path.basename(rootPath);

  7.  packages = getPackageToInstall(packages);

  8.  checkGameName(gameName);

  9.  /**

  10.   2. 判断文件夹是否存在并创建

  11.  */

  12.  fs.ensureDirSync(rootPath);

  13.  /**

  14.   3. 非空文件夹则退出

  15. */

  16.  if (!emptyDirectory(rootPath)) {

  17.    console.log(`The directory ${chalk.green(gameName)} is not empty.`);

  18.    console.log();

  19.    console.log('Either try using a new directory name, or remove the files.');

  20.    process.exit(1);

  21.  }

  22.  /**

  23.   4. 创建 package.json

  24. */

  25.  const packageJson = {

  26.    name: gameName,

  27.    version: '0.1.0',

  28.    private: true,

  29.  };

  30.  fs.writeFileSync(

  31.    path.join(rootPath, 'package.json'),

  32.    JSON.stringify(packageJson, null, 2) + os.EOL

  33.  );

  34.  const useYarn = shouldUseYarn();

  35.  run(rootPath, gameName, packages, useYarn);

  36. }

 
   
   
 
  1. function run(root, gameName, packages, useYarn) {

  2.  const allDependencies = [

  3.    'babel-polyfill',

  4.    'vue',

  5.    'vue-router',

  6.    'vuex',

  7.    'meisha-fe-watch',

  8.    'katex',

  9.    ...packages,

  10.  ];

  11.  const allDevDependencies = ['steam-game-scripts'];

  12.  console.log('Installing packages. This might take a couple of minutes.');

  13.  checkIfOnline(useYarn)

  14.    .then(isOnline => {

  15.      console.log(`Installing ${chalk.cyan(allDependencies.join(' '))}...`);

  16.      console.log();

  17.      return install(root, useYarn, allDependencies, isOnline).then(() =>

  18.        install(root, useYarn, allDevDependencies, isOnline, true)

  19.      );

  20.    })

  21.    .then(async () => {

  22.      await executeNodeScript(

  23.        {

  24.          cwd: root,

  25.          args: [],

  26.        },

  27.        [root, gameName],

  28.        `

  29.      var init = require('steam-game-scripts/scripts/init.js');

  30.      init.apply(null, JSON.parse(process.argv[1]));

  31.      `

  32.      );

  33.    })

  34.    .catch(reason => {

  35.      console.log();

  36.      console.log('Aborting installation.');

  37.      if (reason.command) {

  38.        console.log(`  ${chalk.cyan(reason.command)} has failed.`);

  39.      } else {

  40.        console.log(chalk.red('Unexpected error. Please report it as a bug:'));

  41.        console.log(reason);

  42.      }

  43.      console.log();

  44.      // On 'exit' we will delete these files from target directory.

  45.      const knownGeneratedFiles = ['package.json', 'yarn.lock', 'node_modules'];

  46.      const currentFiles = fs.readdirSync(path.join(root));

  47.      currentFiles.forEach(file => {

  48.        knownGeneratedFiles.forEach(fileToMatch => {

  49.          // This remove all of knownGeneratedFiles.

  50.          if (file === fileToMatch) {

  51.            console.log(`Deleting generated file... ${chalk.cyan(file)}`);

  52.            fs.removeSync(path.join(root, file));

  53.          }

  54.        });

  55.      });

  56.      const remainingFiles = fs.readdirSync(path.join(root));

  57.      if (!remainingFiles.length) {

  58.        // Delete target folder if empty

  59.        console.log(

  60.          `Deleting ${chalk.cyan(`${gameName}/`)} from ${chalk.cyan(

  61.            path.resolve(root, '..')

  62.          )}`

  63.        );

  64.        process.chdir(path.resolve(root, '..'));

  65.        fs.removeSync(path.join(root));

  66.      }

  67.      console.log('Done.');

  68.      process.exit(1);

  69.    });

  70. }

安装好之后就可以进入开发了!

steam-game-scripts

steam-game-scripts 的主要工作是负责搭建和打包项目。

 
   
   
 
  1. steam-game-scripts start # 开发

  2. steam-game-scripts start --gameName # 开发单个游戏

  3. steam-game-scripts build --gameName # 打包单个游戏

  4. steam-game-scripts test # 测试

由于我司是使用自建的发布系统进行项目的打包发布。于是需要在发布系统上在打包的时候添加自定义的 npm 执行命令。所以这里使用了 --options 的方式来支持单个游戏的打包。也可以搭建多个新游戏的项目仓库来分开打包。

 
   
   
 
  1. const scriptIndex = args.findIndex(

  2.  x => x === 'build' || x === 'start' || x === 'test'

  3. );

  4. const script = scriptIndex === -1 ? args[0] : args[scriptIndex];

  5. const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];

  6. switch (script) {

  7.  case 'build':

  8.  case 'start':

  9.  case 'init':

  10.  case 'test': {

  11.    const result = spawn.sync(

  12.      'node',

  13.      nodeArgs

  14.        .concat(require.resolve('../scripts/' + script))

  15.        .concat(args.slice(scriptIndex + 1)),

  16.      { stdio: 'inherit' }

  17.    );

  18.    if (result.signal) {

  19.      if (result.signal === 'SIGKILL') {

  20.        console.log(

  21.          'The build failed because the process exited too early. ' +

  22.            'This probably means the system ran out of memory or someone called ' +

  23.            '`kill -9` on the process.'

  24.        );

  25.      } else if (result.signal === 'SIGTERM') {

  26.        console.log(

  27.          'The build failed because the process exited too early. ' +

  28.            'Someone might have called `kill` or `killall`, or the system could ' +

  29.            'be shutting down.'

  30.        );

  31.      }

  32.      process.exit(1);

  33.    }

  34.    process.exit(result.status);

  35.  }

  36.  default:

  37.    console.log('Unknown script "' + script + '".');

  38.    console.log('Perhaps you need to update steam-game-scripts?');

  39.    break;

  40. }

steam-game-scripts/scripts/start.js

 
   
   
 
  1. 'use strict';

  2. const WebpackDevServer = require('webpack-dev-server');

  3. const webpack = require('webpack');

  4. const webpackConfig = require('../build/webpack.dev.conf.js');

  5. const createDevServerConfig = require('../build/webpackDevServer.config.js');

  6. const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');

  7. const portfinder = require('portfinder');

  8. const config = require('../config');

  9. const utils = require('../build/utils');

  10. const HOST = process.env.HOST || '0.0.0.0';

  11. portfinder.basePort = process.env.PORT || config.dev.port;

  12. portfinder.getPort((err, port) => {

  13.  if (err) {

  14.  } else {

  15.    const devServerConfig = createDevServerConfig(port);

  16.    webpackConfig.plugins.push(

  17.      new FriendlyErrorsPlugin({

  18.        compilationSuccessInfo: {

  19.          messages: [

  20.            `Your application is running here: http://${

  21.              config.dev.host

  22.            }:${port}`,

  23.          ],

  24.        },

  25.        onErrors: config.dev.notifyOnErrors

  26.          ? utils.createNotifierCallback()

  27.          : undefined,

  28.      })

  29.    );

  30.    var compiler = webpack(webpackConfig);

  31.    var devServer = new WebpackDevServer(compiler, devServerConfig);

  32.    devServer.listen(port, HOST, err => {

  33.      if (err) {

  34.        return console.log(err);

  35.      }

  36.    });

  37.    ['SIGINT', 'SIGTERM'].forEach(function(sig) {

  38.      process.on(sig, function() {

  39.        devServer.close();

  40.        process.exit();

  41.      });

  42.    });

  43.  }

  44. });

未完待续...

以上是关于参考 lerna 的方式拆分类单页应用的主要内容,如果未能解决你的问题,请参考以下文章

单页应用及多页应用

[Angularjs]asp.net mvc+angularjs+web api单页应用之CRUD操作

Vue 创建多页面应用模式

单页应用程序路由

如果更改单页应用程序以使 2 条路由在拆分视图中都可见,是不是可以使用 Angular 路由?

保护私有反应组件