"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.watcherIgnoreRegExp = void 0;

function _react() {
  const data = _interopRequireDefault(require("react"));

  _react = function _react() {
    return data;
  };

  return data;
}

function _path() {
  const data = require("path");

  _path = function _path() {
    return data;
  };

  return data;
}

function _fs() {
  const data = require("fs");

  _fs = function _fs() {
    return data;
  };

  return data;
}

function _mkdirp() {
  const data = _interopRequireDefault(require("mkdirp"));

  _mkdirp = function _mkdirp() {
    return data;
  };

  return data;
}

function _chokidar() {
  const data = _interopRequireDefault(require("chokidar"));

  _chokidar = function _chokidar() {
    return data;
  };

  return data;
}

function _assert() {
  const data = _interopRequireDefault(require("assert"));

  _assert = function _assert() {
    return data;
  };

  return data;
}

function _chalk() {
  const data = _interopRequireDefault(require("chalk"));

  _chalk = function _chalk() {
    return data;
  };

  return data;
}

function _lodash() {
  const data = require("lodash");

  _lodash = function _lodash() {
    return data;
  };

  return data;
}

function _mustache() {
  const data = _interopRequireDefault(require("mustache"));

  _mustache = function _mustache() {
    return data;
  };

  return data;
}

function _umiUtils() {
  const data = require("umi-utils");

  _umiUtils = function _umiUtils() {
    return data;
  };

  return data;
}

var _stripJSONQuote = _interopRequireDefault(require("./routes/stripJSONQuote"));

var _routesToJSON = _interopRequireDefault(require("./routes/routesToJSON"));

var _importsToStr = _interopRequireDefault(require("./importsToStr"));

var _constants = require("./constants");

var _getHtmlGenerator = _interopRequireDefault(require("./plugins/commands/getHtmlGenerator"));

var _htmlToJSX = _interopRequireDefault(require("./htmlToJSX"));

var _getRoutePaths = _interopRequireDefault(require("./routes/getRoutePaths"));

var _writeContent = _interopRequireDefault(require("./utils/writeContent.js"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const debug = require('debug')('umi:FilesGenerator');

const watcherIgnoreRegExp = /(^|[\/\\])(_mock.js$|\..)/;
exports.watcherIgnoreRegExp = watcherIgnoreRegExp;

function normalizePath(path, base = '/') {
  if (path.startsWith(base)) {
    path = path.replace(base, '/');
  }

  return path;
}

class FilesGenerator {
  constructor(opts) {
    Object.keys(opts).forEach(key => {
      this[key] = opts[key];
    });
    this.routesContent = null;
    this.hasRebuildError = false;
  }

  generate() {
    debug('generate');
    const paths = this.service.paths;
    const absTmpDirPath = paths.absTmpDirPath,
          tmpDirPath = paths.tmpDirPath;
    debug(`mkdir tmp dir: ${tmpDirPath}`);

    _mkdirp().default.sync(absTmpDirPath);

    this.generateFiles();
  }

  createWatcher(path) {
    const watcher = _chokidar().default.watch(path, {
      ignored: watcherIgnoreRegExp,
      // ignore .dotfiles and _mock.js
      ignoreInitial: true
    });

    watcher.on('all', (0, _lodash().throttle)((event, path) => {
      debug(`${event} ${path}`);
      this.rebuild();
    }, 100));
    return watcher;
  }

  watch() {
    if (process.env.WATCH_FILES === 'none') return;
    const _this$service = this.service,
          paths = _this$service.paths,
          singular = _this$service.config.singular;
    const layout = singular ? 'layout' : 'layouts';
    let pageWatchers = [paths.absPagesPath, ..._constants.EXT_LIST.map(ext => (0, _path().join)(paths.absSrcPath, `${layout}/index${ext}`)), ..._constants.EXT_LIST.map(ext => (0, _path().join)(paths.absSrcPath, `app${ext}`))];

    if (this.modifyPageWatcher) {
      pageWatchers = this.modifyPageWatcher(pageWatchers);
    }

    this.watchers = pageWatchers.map(p => {
      return this.createWatcher(p);
    });
    process.on('SIGINT', () => {
      this.unwatch();
    });
  }

  unwatch() {
    if (this.watchers) {
      this.watchers.forEach(watcher => {
        watcher.close();
      });
    }
  }

  rebuild() {
    const _this$service2 = this.service,
          refreshBrowser = _this$service2.refreshBrowser,
          printError = _this$service2.printError;
    const isDev = process.env.NODE_ENV === 'development';

    try {
      this.service.applyPlugins('onGenerateFiles', {
        args: {
          isRebuild: true
        }
      });
      this.generateRouterJS();
      this.generateEntry();
      this.generateHistory();

      if (this.hasRebuildError) {
        if (isDev) refreshBrowser();
        this.hasRebuildError = false;
      }
    } catch (e) {
      // 向浏览器发送出错信息
      if (isDev) printError([e.message]);
      this.hasRebuildError = true;
      this.routesContent = null; // why?

      debug(`Generate failed: ${e.message}`);
      debug(e);
      console.error(_chalk().default.red(e.message));
    }
  }

  generateFiles() {
    this.service.applyPlugins('onGenerateFiles');
    this.generateRouterJS();
    this.generateEntry();
    this.generateHistory();
  }

  generateEntry() {
    const _this$service3 = this.service,
          paths = _this$service3.paths,
          config = _this$service3.config; // Generate umi.js

    const entryTpl = (0, _fs().readFileSync)(paths.defaultEntryTplPath, 'utf-8');
    const initialRender = this.service.applyPlugins('modifyEntryRender', {
      initialValue: `
  window.g_isBrowser = true;
  let props = {};
  // Both support SSR and CSR
  if (window.g_useSSR) {
    // 如果开启服务端渲染则客户端组件初始化 props 使用服务端注入的数据
    props = window.g_initialData;
  } else {
    const pathname = location.pathname;
    const activeRoute = findRoute(require('@@/router').routes, pathname);
    // 在客户端渲染前，执行 getInitialProps 方法
    // 拿到初始数据
    if (activeRoute && activeRoute.component && activeRoute.component.getInitialProps) {
      const initialProps = plugins.apply('modifyInitialProps', {
        initialValue: {},
      });
      props = activeRoute.component.getInitialProps ? await activeRoute.component.getInitialProps({
        route: activeRoute,
        isServer: false,
        location,
        ...initialProps,
      }) : {};
    }
  }
  const rootContainer = plugins.apply('rootContainer', {
    initialValue: React.createElement(require('./router').default, props),
  });
  ReactDOM[window.g_useSSR ? 'hydrate' : 'render'](
    rootContainer,
    document.getElementById('${config.mountElementId}'),
  );
      `.trim()
    });
    const moduleBeforeRenderer = this.service.applyPlugins('addRendererWrapperWithModule', {
      initialValue: []
    }).map((source, index) => {
      return {
        source,
        specifier: `moduleBeforeRenderer${index}`
      };
    });
    const plugins = this.service.applyPlugins('addRuntimePlugin', {
      initialValue: []
    }).map(plugin => {
      return (0, _umiUtils().winPath)((0, _path().relative)(paths.absTmpDirPath, plugin));
    });

    if ((0, _umiUtils().findJS)(paths.absSrcPath, 'app')) {
      plugins.push('@/app');
    }

    const validKeys = this.service.applyPlugins('addRuntimePluginKey', {
      initialValue: ['patchRoutes', 'render', 'rootContainer', 'modifyRouteProps', 'onRouteChange', 'modifyInitialProps', 'initialProps']
    });
    (0, _assert().default)((0, _lodash().uniq)(validKeys).length === validKeys.length, `Conflict keys found in [${validKeys.join(', ')}]`);
    let htmlTemplateMap = [];

    if (config.ssr) {
      const isProd = process.env.NODE_ENV === 'production';
      const routePaths = (0, _getRoutePaths.default)(this.RoutesManager.routes);
      htmlTemplateMap = routePaths.map(routePath => {
        let ssrHtml = '<></>';
        const hg = (0, _getHtmlGenerator.default)(this.service, {
          chunksMap: {
            // TODO, for dynamic chunks
            // placeholder waiting manifest
            umi: [isProd ? '__UMI_SERVER__.js' : 'umi.js', isProd ? '__UMI_SERVER__.css' : 'umi.css']
          },
          headScripts: [{
            content: 'window.g_useSSR=true;'.trim()
          }],
          scripts: [{
            content: `window.g_initialData = \${stringify(props)};`.trim()
          }]
        });
        const content = hg.getMatchedContent(normalizePath(routePath, config.base));
        ssrHtml = (0, _htmlToJSX.default)(content).replace(`<div id="${config.mountElementId || 'root'}"></div>`, `<div id="${config.mountElementId || 'root'}">{rootContainer}</div>`);
        return `'${routePath}': (${ssrHtml}),`;
      });
    }

    const entryContent = _mustache().default.render(entryTpl, {
      globalVariables: !this.service.config.disableGlobalVariables,
      code: this.service.applyPlugins('addEntryCode', {
        initialValue: []
      }).join('\n\n'),
      codeAhead: this.service.applyPlugins('addEntryCodeAhead', {
        initialValue: []
      }).join('\n\n'),
      imports: (0, _importsToStr.default)(this.service.applyPlugins('addEntryImport', {
        initialValue: moduleBeforeRenderer
      })).join('\n'),
      importsAhead: (0, _importsToStr.default)(this.service.applyPlugins('addEntryImportAhead', {
        initialValue: []
      })).join('\n'),
      polyfillImports: (0, _importsToStr.default)(this.service.applyPlugins('addEntryPolyfillImports', {
        initialValue: []
      })).join('\n'),
      moduleBeforeRenderer,
      render: initialRender,
      plugins,
      validKeys,
      htmlTemplateMap: htmlTemplateMap.join('\n'),
      findRoutePath: (0, _umiUtils().winPath)(require.resolve('./findRoute'))
    });

    (0, _writeContent.default)(paths.absLibraryJSPath, (0, _umiUtils().prettierFile)(`${entryContent.trim()}\n`));
  }

  generateHistory() {
    const _this$service4 = this.service,
          paths = _this$service4.paths,
          config = _this$service4.config;
    const tpl = (0, _fs().readFileSync)(paths.defaultHistoryTplPath, 'utf-8');
    const initialHistory = `
require('umi/lib/createHistory').default({
  basename: window.routerBase,
})
    `.trim();
    let history = this.service.applyPlugins('modifyEntryHistory', {
      initialValue: initialHistory
    });

    if (config.ssr) {
      history = `
__IS_BROWSER ? ${initialHistory} : require('history').createMemoryHistory({
  // for history object in dva
  initialEntries: [global.req ? global.req.url : '/']
})
      `.trim();
    }

    const content = _mustache().default.render(tpl, {
      globalVariables: !this.service.config.disableGlobalVariables,
      history
    });

    (0, _writeContent.default)((0, _path().join)(paths.absTmpDirPath, 'history.js'), (0, _umiUtils().prettierFile)(`${content.trim()}\n`));
  }

  generateRouterJS() {
    const paths = this.service.paths;
    const absRouterJSPath = paths.absRouterJSPath;
    this.RoutesManager.fetchRoutes();
    const routesContent = this.getRouterJSContent(); // 避免文件写入导致不必要的 webpack 编译

    if (this.routesContent !== routesContent) {
      (0, _fs().writeFileSync)(absRouterJSPath, (0, _umiUtils().prettierFile)(`${routesContent.trim()}\n`), 'utf-8');
      this.routesContent = routesContent;
      this.service.applyPlugins('onRouteChange');
    }
  }

  getRouterJSContent() {
    const paths = this.service.paths;
    const routerTpl = (0, _fs().readFileSync)(paths.defaultRouterTplPath, 'utf-8');
    const routes = (0, _stripJSONQuote.default)(this.getRoutesJSON({
      env: process.env.NODE_ENV
    }));
    const rendererWrappers = this.service.applyPlugins('addRendererWrapperWithComponent', {
      initialValue: []
    }).map((source, index) => {
      return {
        source,
        specifier: `RendererWrapper${index}`
      };
    });
    const routerContent = this.getRouterContent(rendererWrappers);
    return _mustache().default.render(routerTpl, {
      globalVariables: !this.service.config.disableGlobalVariables,
      imports: (0, _importsToStr.default)(this.service.applyPlugins('addRouterImport', {
        initialValue: rendererWrappers
      })).join('\n'),
      importsAhead: (0, _importsToStr.default)(this.service.applyPlugins('addRouterImportAhead', {
        initialValue: []
      })).join('\n'),
      routes,
      routerContent,
      RouterRootComponent: this.service.applyPlugins('modifyRouterRootComponent', {
        initialValue: 'DefaultRouter'
      })
    });
  }

  fixHtmlSuffix(routes) {
    routes.forEach(route => {
      if (route.routes) {
        route.path = `${route.path}(.html)?`;
        this.fixHtmlSuffix(route.routes);
      }
    });
  }

  getRoutesJSON(opts = {}) {
    const env = opts.env;
    return (0, _routesToJSON.default)(this.RoutesManager.routes, this.service, env);
  }

  getRouterContent(rendererWrappers) {
    const config = this.service.config;
    let defaultRenderer = `
      <Router history={history}>{renderRoutes(routes, props)}</Router>
    `.trim();

    if (config.ssr) {
      // 若有 RendererWrapper ，是使用 {__IS_BROWSER ? : }
      // 没有时得使用 __IS_BROWSER ? :
      // 所以使用 Fragment 包一下
      defaultRenderer = `
      <>
        {__IS_BROWSER
          ? <Router history={history}>{renderRoutes(routes, props)}</Router>
          : renderRoutes(routes, props)
        }
      </>
      `.trim();
    }

    return rendererWrappers.reduce((memo, wrapper) => {
      return `
        <${wrapper.specifier}>
          ${memo}
        </${wrapper.specifier}>
      `.trim();
    }, defaultRenderer);
  }

}

exports.default = FilesGenerator;