From 9d07a89c480fb7c7eaf67c8b741abc0502c56b5f Mon Sep 17 00:00:00 2001 From: luckyadam Date: Fri, 14 Jun 2019 11:03:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(cli):=20=E6=94=AF=E6=8C=81=E5=BC=95?= =?UTF-8?q?=E7=94=A8=20node=5Fmodules=20=E4=B8=AD=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/taro-mini-runner/package.json | 1 + .../dependencies/TaroSingleEntryDependency.ts | 17 ++ packages/taro-mini-runner/src/index.ts | 1 - .../src/loaders/fileParseLoader.ts | 8 +- .../src/plugins/MiniPlugin.ts | 232 ++++++++++++++++-- .../src/plugins/ResolverPlugin.ts | 28 +++ .../src/plugins/TaroLoadChunksPlugin.ts | 6 +- .../src/plugins/TaroNormalModule.ts | 12 + .../src/plugins/TaroNormalModulesPlugin.ts | 19 ++ .../taro-mini-runner/src/utils/constants.ts | 3 + packages/taro-mini-runner/src/utils/index.ts | 21 +- packages/taro-mini-runner/yarn.lock | 6 + 12 files changed, 324 insertions(+), 30 deletions(-) create mode 100644 packages/taro-mini-runner/src/dependencies/TaroSingleEntryDependency.ts create mode 100644 packages/taro-mini-runner/src/plugins/ResolverPlugin.ts create mode 100644 packages/taro-mini-runner/src/plugins/TaroNormalModule.ts create mode 100644 packages/taro-mini-runner/src/plugins/TaroNormalModulesPlugin.ts diff --git a/packages/taro-mini-runner/package.json b/packages/taro-mini-runner/package.json index 1472a4007fbc..b3ef3b8d5102 100644 --- a/packages/taro-mini-runner/package.json +++ b/packages/taro-mini-runner/package.json @@ -49,6 +49,7 @@ "lodash": "^4.17.11", "node-sass": "^4.12.0", "ora": "^3.4.0", + "resolve": "^1.11.1", "sass-loader": "^7.1.0", "virtual-module-webpack-plugin": "^0.4.1", "webpack": "^4.31.0", diff --git a/packages/taro-mini-runner/src/dependencies/TaroSingleEntryDependency.ts b/packages/taro-mini-runner/src/dependencies/TaroSingleEntryDependency.ts new file mode 100644 index 000000000000..70e251a37ef2 --- /dev/null +++ b/packages/taro-mini-runner/src/dependencies/TaroSingleEntryDependency.ts @@ -0,0 +1,17 @@ +import * as ModuleDependency from 'webpack/lib/dependencies/ModuleDependency' +import { PARSE_AST_TYPE } from '../utils/constants' + +export default class TaroSingleEntryDependency extends ModuleDependency { + name: string + miniType: PARSE_AST_TYPE + constructor (request, name, loc, miniType) { + super(request) + this.name = name + this.loc = loc + this.miniType = miniType + } + + get type () { + return 'single entry' + } +} diff --git a/packages/taro-mini-runner/src/index.ts b/packages/taro-mini-runner/src/index.ts index 7cebfcabc2e3..17a0b0c08f60 100644 --- a/packages/taro-mini-runner/src/index.ts +++ b/packages/taro-mini-runner/src/index.ts @@ -36,7 +36,6 @@ export default function build (config: IBuildConfig) { rules: [ { test: /\.(tsx?|jsx?)$/, - exclude: /node_modules/, use: [{ loader: path.resolve(__dirname, './loaders/fileParseLoader'), options: { diff --git a/packages/taro-mini-runner/src/loaders/fileParseLoader.ts b/packages/taro-mini-runner/src/loaders/fileParseLoader.ts index c7359c35242a..cd333e12d3b6 100644 --- a/packages/taro-mini-runner/src/loaders/fileParseLoader.ts +++ b/packages/taro-mini-runner/src/loaders/fileParseLoader.ts @@ -449,8 +449,7 @@ export default function fileParseLoader (source, ast) { constantsReplaceList, buildAdapter, designWidth, - deviceRatio, - fileTypeMap + deviceRatio } = getOptions(this) const filePath = this.resourcePath const newAst = transformFromAst(ast, '', { @@ -459,9 +458,8 @@ export default function fileParseLoader (source, ast) { [require('babel-plugin-transform-define').default, constantsReplaceList] ] }).ast as t.File - const fileTypeInfo = fileTypeMap[filePath] || { type: PARSE_AST_TYPE.NORMAL, config: { } } - const configObj = fileTypeInfo.config - const result = processAst(newAst, buildAdapter, fileTypeInfo.type, designWidth, deviceRatio, filePath, this.context) + const miniType = this._module.miniType || PARSE_AST_TYPE.NORMAL + const result = processAst(newAst, buildAdapter, miniType, designWidth, deviceRatio, filePath, this.context) const code = generate(result).code const res = transform(code, babelConfig) return res.code diff --git a/packages/taro-mini-runner/src/plugins/MiniPlugin.ts b/packages/taro-mini-runner/src/plugins/MiniPlugin.ts index 7f15613ab98a..0ac6a6bc0ad1 100644 --- a/packages/taro-mini-runner/src/plugins/MiniPlugin.ts +++ b/packages/taro-mini-runner/src/plugins/MiniPlugin.ts @@ -10,15 +10,19 @@ import * as JsonpTemplatePlugin from 'webpack/lib/web/JsonpTemplatePlugin' import * as NodeSourcePlugin from 'webpack/lib/node/NodeSourcePlugin' import * as LoaderTargetPlugin from 'webpack/lib/LoaderTargetPlugin' import * as VirtualModulePlugin from 'virtual-module-webpack-plugin' -import { merge, defaults } from 'lodash' +import { merge, defaults, kebabCase } from 'lodash' import * as t from 'babel-types' import traverse from 'babel-traverse' import { Config as IConfig } from '@tarojs/taro' -import { REG_TYPESCRIPT, BUILD_TYPES, PARSE_AST_TYPE, MINI_APP_FILES } from '../utils/constants' -import { traverseObjectNode, resolveScriptPath, buildUsingComponents } from '../utils' +import { REG_TYPESCRIPT, BUILD_TYPES, PARSE_AST_TYPE, MINI_APP_FILES, NODE_MODULES_REG } from '../utils/constants' +import { IComponentObj } from '../utils/types' +import { traverseObjectNode, resolveScriptPath, buildUsingComponents, isNpmPkg, resolveNpmSync } from '../utils' +import TaroSingleEntryDependency from '../dependencies/TaroSingleEntryDependency' import TaroLoadChunksPlugin from './TaroLoadChunksPlugin' +import TaroNormalModulesPlugin from './TaroNormalModulesPlugin' +import ResolverPlugin from './ResolverPlugin' interface IMiniPluginOptions { appEntry?: string, @@ -59,12 +63,64 @@ export const Targets = { [BUILD_TYPES.QQ]: createTarget(BUILD_TYPES.QQ), } +export function isFileToBeTaroComponent ( + code: string, + sourcePath: string, + buildAdapter: BUILD_TYPES +) { + const transformResult = wxTransformer({ + code, + sourcePath: sourcePath, + isTyped: REG_TYPESCRIPT.test(sourcePath), + adapter: buildAdapter, + isNormal: true + }) + const { ast } = transformResult + let isTaroComponent = false + + traverse(ast, { + ClassDeclaration (astPath) { + astPath.traverse({ + ClassMethod (astPath) { + if (astPath.get('key').isIdentifier({ name: 'render' })) { + astPath.traverse({ + JSXElement () { + isTaroComponent = true + } + }) + } + } + }) + }, + + ClassExpression (astPath) { + astPath.traverse({ + ClassMethod (astPath) { + if (astPath.get('key').isIdentifier({ name: 'render' })) { + astPath.traverse({ + JSXElement () { + isTaroComponent = true + } + }) + } + } + }) + } + }) + + return { + isTaroComponent, + transformResult + } +} + export default class MiniPlugin { options: IMiniPluginOptions appEntry: string pages: Set components: Set sourceDir: string + context: string constructor (options = {}) { this.options = defaults(options || {}, { @@ -72,7 +128,7 @@ export default class MiniPlugin { commonChunks: ['runtime', 'vendors'] }) - this.pages = new Set() + this.pages = new Set() this.components = new Set() } @@ -86,6 +142,8 @@ export default class MiniPlugin { } apply (compiler: webpack.Compiler) { + this.context = compiler.context + compiler.hooks.run.tapAsync( PLUGIN_NAME, this.tryAsync(async (compiler: webpack.Compiler) => { @@ -93,17 +151,51 @@ export default class MiniPlugin { }) ) - compiler.hooks.emit.tapAsync( + compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set(TaroSingleEntryDependency, normalModuleFactory) + // compilation.hooks.afterOptimizeChunks.tap(PLUGIN_NAME, (modules) => { + // console.log(modules) + // // modules.forEach(mod => { + // // console.log(mod.name) + // // console.log(mod._source.source()) + // // }) + // // callback() + // }) + }) + + compiler.hooks.emit.tapAsync( PLUGIN_NAME, this.tryAsync(async compilation => { await this.generateMiniFiles(compilation) }) ) + // compiler.resolverFactory.hooks.resolver.for('normal').tap(PLUGIN_NAME, resolver => { + // console.log('sdsdsdsdsd') + // new ResolverPlugin('resolve', 'parsedResolve').apply(resolver) + // }) + // compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, nmf => { + // nmf.hooks.afterResolve.tapAsync(PLUGIN_NAME, (result, callback) => { + // // console.log(result) + // if (result.resource.indexOf('node_modules') >= 0) { + // const issuerArr = result.resource.split(path.sep) + // const lastNodeModulesIndex = issuerArr.lastIndexOf('node_modules') + // const pkgName = result.resourceResolveData.descriptionFileData.name + // issuerArr.splice(lastNodeModulesIndex + 1, pkgName.split('/').length, pkgName.replace(/\//g, path.sep)) + // const newIssuer = issuerArr.join(path.sep) + // result.userRequest = newIssuer + // result.resource = newIssuer + // } + // return callback(null, result) + // }) + // }) + new TaroLoadChunksPlugin({ commonChunks: this.options.commonChunks, taroFileTypeMap }).apply(compiler) + + new TaroNormalModulesPlugin().apply(compiler) } getAppEntry (compiler) { @@ -185,6 +277,95 @@ export default class MiniPlugin { } } + getNpmComponentRealPath (code: string, component: IComponentObj, adapter: BUILD_TYPES): string | null { + let componentRealPath: string | null = null + let importExportName + const { isTaroComponent, transformResult } = isFileToBeTaroComponent(code, component.path, adapter) + if (isTaroComponent) { + return component.path + } + const { ast } = transformResult + traverse(ast, { + ExportNamedDeclaration (astPath) { + const node = astPath.node + const specifiers = node.specifiers + const source = node.source + if (source && source.type === 'StringLiteral') { + specifiers.forEach(specifier => { + const exported = specifier.exported + if (kebabCase(exported.name) === component.name) { + componentRealPath = resolveScriptPath(path.resolve(path.dirname(component.path), source.value)) + } + }) + } else { + specifiers.forEach(specifier => { + const exported = specifier.exported + if (kebabCase(exported.name) === component.name) { + importExportName = exported.name + } + }) + } + }, + + ExportDefaultDeclaration (astPath) { + const node = astPath.node + const declaration = node.declaration as t.Identifier + if (component.type === 'default') { + importExportName = declaration.name + } + }, + + CallExpression (astPath) { + if (astPath.get('callee').isIdentifier({ name: 'require' })) { + const arg = astPath.get('arguments')[0] + if (t.isStringLiteral(arg.node)) { + componentRealPath = resolveScriptPath(path.resolve(path.dirname(component.path), arg.node.value)) + } + } + }, + + Program: { + exit (astPath) { + astPath.traverse({ + ImportDeclaration (astPath) { + const node = astPath.node + const specifiers = node.specifiers + const source = node.source + if (importExportName) { + specifiers.forEach(specifier => { + const local = specifier.local + if (local.name === importExportName) { + componentRealPath = resolveScriptPath(path.resolve(path.dirname(component.path), source.value)) + } + }) + } + } + }) + } + } + }) + if (componentRealPath) { + component.path = componentRealPath + code = fs.readFileSync(componentRealPath).toString() + componentRealPath = this.getNpmComponentRealPath(code, component, adapter) + } + return componentRealPath + } + + transfromComponentsPath (components: IComponentObj[]) { + const { buildAdapter } = this.options + components.forEach(component => { + const componentPath = component.path + if (componentPath && isNpmPkg(componentPath)) { + const res = resolveNpmSync(componentPath, this.context) + const code = fs.readFileSync(res).toString() + const newComponent = Object.assign({}, component, { path: res }) + const realComponentPath = this.getNpmComponentRealPath(code, newComponent, buildAdapter) + component.path = realComponentPath + } + }) + } + getPages () { const { buildAdapter } = this.options const appEntry = this.appEntry @@ -228,18 +409,25 @@ export default class MiniPlugin { }) const { configObj } = this.parseAst(transformResult.ast, buildAdapter) const isComponentConfig = isRoot ? {} : { component: true } + let depComponents = transformResult.components + this.transfromComponentsPath(depComponents) taroFileTypeMap[file.path] = { type: isRoot ? PARSE_AST_TYPE.PAGE : PARSE_AST_TYPE.COMPONENT, - config: merge({}, isComponentConfig, buildUsingComponents(file.path, {}, transformResult.components),configObj), + config: merge({}, isComponentConfig, buildUsingComponents(file.path, this.sourceDir, {}, depComponents),configObj), template: transformResult.template, code: transformResult.code } - let depComponents = transformResult.components if (depComponents && depComponents.length) { depComponents.forEach(item => { const componentPath = resolveScriptPath(path.resolve(path.dirname(file.path), item.path)) if (fs.existsSync(componentPath) && !Array.from(this.components).some(item => item.path === componentPath)) { - const componentName = componentPath.replace(this.sourceDir, '').replace(/\\/g, '/').replace(path.extname(componentPath), '') + let componentName + if (NODE_MODULES_REG.test(componentPath)) { + componentName = componentPath.replace(this.context, '').replace(/\\/g, '/').replace(path.extname(componentPath), '') + componentName = componentName.replace(/node_modules/gi, 'npm') + } else { + componentName = componentPath.replace(this.sourceDir, '').replace(/\\/g, '/').replace(path.extname(componentPath), '') + } const componentObj = { name: componentName, path: componentPath } this.components.add(componentObj) this.getComponents(new Set([componentObj]), false) @@ -249,24 +437,32 @@ export default class MiniPlugin { }) } + addEntry (compiler: webpack.Compiler, entryPath, entryName, entryType) { + compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation: webpack.compilation.Compilation, callback) => { + const dep = new TaroSingleEntryDependency(entryPath, entryName, { name: entryName }, entryType) + compilation.addEntry(this.sourceDir, dep, entryName, callback) + }) + } + addEntries (compiler: webpack.Compiler) { - const mainFiles = new Set([ ...this.pages, ...this.components ]) - mainFiles.add({ - name: 'app', - path: this.appEntry + this.addEntry(compiler, this.appEntry, 'app', PARSE_AST_TYPE.ENTRY) + this.pages.forEach(item => { + this.addEntry(compiler, item.path, item.name, PARSE_AST_TYPE.PAGE) }) - mainFiles.forEach(item => { - compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation: webpack.compilation.Compilation, callback) => { - const dep = SingleEntryPlugin.createDependency(item.path, item.name) - compilation.addEntry(this.sourceDir, dep, item.name, callback) - }) + this.components.forEach(item => { + this.addEntry(compiler, item.path, item.name, PARSE_AST_TYPE.COMPONENT) }) } generateMiniFiles (compilation: webpack.compilation.Compilation) { const { buildAdapter } = this.options Object.keys(taroFileTypeMap).forEach(item => { - const relativePath = item.replace(this.sourceDir, '') + let relativePath + if (NODE_MODULES_REG.test(item)) { + relativePath = item.replace(this.context, '').replace(/node_modules/gi, 'npm') + } else { + relativePath = item.replace(this.sourceDir, '') + } const extname = path.extname(item) const templatePath = relativePath.replace(extname, MINI_APP_FILES[buildAdapter].TEMPL) const jsonPath = relativePath.replace(extname, MINI_APP_FILES[buildAdapter].CONFIG) diff --git a/packages/taro-mini-runner/src/plugins/ResolverPlugin.ts b/packages/taro-mini-runner/src/plugins/ResolverPlugin.ts new file mode 100644 index 000000000000..4f5f7e6362d0 --- /dev/null +++ b/packages/taro-mini-runner/src/plugins/ResolverPlugin.ts @@ -0,0 +1,28 @@ +import * as path from 'path' +import { NODE_MODULES_REG } from '../utils/constants' + +const PLUGIN_NAME = 'ResolverPlugin' + +export default class ResolverPlugin { + constructor (source, target) { + this.source = source + this.target = target + } + + apply (resolver) { + const source = resolver.getHook(this.source) + const target = resolver.ensureHook(this.target) + source.tapAsync(PLUGIN_NAME, (request, resolveContext, callback) => { + const { issuer } = request.context + if (issuer && NODE_MODULES_REG.test(issuer)) { + const issuerArr = issuer.split(path.sep) + const lastNodeModulesIndex = issuerArr.lastIndexOf('node_modules') + const pkgName = request.descriptionFileData.name + issuerArr.splice(lastNodeModulesIndex + 1, pkgName.split('/').length, pkgName.replace(/\//g, path.sep)) + const newIssuer = issuerArr.join(path.sep) + request.context.issuer = newIssuer + } + resolver.doResolve(target, request, null, resolveContext, callback) + }) + } +} diff --git a/packages/taro-mini-runner/src/plugins/TaroLoadChunksPlugin.ts b/packages/taro-mini-runner/src/plugins/TaroLoadChunksPlugin.ts index 17350a670233..b366ad93d2a6 100644 --- a/packages/taro-mini-runner/src/plugins/TaroLoadChunksPlugin.ts +++ b/packages/taro-mini-runner/src/plugins/TaroLoadChunksPlugin.ts @@ -19,7 +19,6 @@ export default class TaroLoadChunksPlugin { constructor (options: IOptions) { this.commonChunks = options.commonChunks - this.taroFileTypeMap = options.taroFileTypeMap } apply (compiler: webpack.Compiler) { @@ -29,10 +28,7 @@ export default class TaroLoadChunksPlugin { commonChunks = chunks.filter(chunk => this.commonChunks.includes(chunk.name)) }) compilation.chunkTemplate.hooks.renderWithEntry.tap(PLUGIN_NAME, (modules, chunk) => { - if (chunk.entryModule - && chunk.entryModule - && this.taroFileTypeMap[chunk.entryModule.resource] - && this.taroFileTypeMap[chunk.entryModule.resource].type === PARSE_AST_TYPE.ENTRY) { + if (chunk.entryModule && chunk.entryModule.miniType === PARSE_AST_TYPE.ENTRY) { const source = new ConcatSource() commonChunks.reverse().forEach(chunkItem => { source.add(`require(${JSON.stringify(urlToRequest(chunkItem.name))});\n`) diff --git a/packages/taro-mini-runner/src/plugins/TaroNormalModule.ts b/packages/taro-mini-runner/src/plugins/TaroNormalModule.ts new file mode 100644 index 000000000000..a5f327d7fd9e --- /dev/null +++ b/packages/taro-mini-runner/src/plugins/TaroNormalModule.ts @@ -0,0 +1,12 @@ +import * as NormalModule from 'webpack/lib/NormalModule' +import { PARSE_AST_TYPE } from '../utils/constants' + +export default class TaroNormalModule extends NormalModule { + name: string + miniType: PARSE_AST_TYPE + constructor (data) { + super(data) + this.name = data.name + this.miniType = data.miniType + } +} diff --git a/packages/taro-mini-runner/src/plugins/TaroNormalModulesPlugin.ts b/packages/taro-mini-runner/src/plugins/TaroNormalModulesPlugin.ts new file mode 100644 index 000000000000..7b99d237a959 --- /dev/null +++ b/packages/taro-mini-runner/src/plugins/TaroNormalModulesPlugin.ts @@ -0,0 +1,19 @@ +import webpack from 'webpack' + +import TaroNormalModule from './TaroNormalModule' +import TaroSingleEntryDependency from '../dependencies/TaroSingleEntryDependency' + +const PLUGIN_NAME = 'TaroNormalModulesPlugin' + +export default class TaroNormalModulesPlugin { + apply (compiler: webpack.Compiler) { + compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, { normalModuleFactory }) => { + normalModuleFactory.hooks.createModule.tap(PLUGIN_NAME, data => { + const dependency = data.dependencies[0] + if (dependency.constructor === TaroSingleEntryDependency) { + return new TaroNormalModule(Object.assign(data, { miniType: dependency.miniType, name: dependency.name })) + } + }) + }) + } +} diff --git a/packages/taro-mini-runner/src/utils/constants.ts b/packages/taro-mini-runner/src/utils/constants.ts index 6c830787a890..2224d8cc9749 100644 --- a/packages/taro-mini-runner/src/utils/constants.ts +++ b/packages/taro-mini-runner/src/utils/constants.ts @@ -9,6 +9,9 @@ export const REG_SCRIPT: RegExp = /\.(js|jsx)(\?.*)?$/ export const REG_TYPESCRIPT: RegExp = /\.(tsx|ts)(\?.*)?$/ export const REG_SCRIPTS: RegExp = /\.[tj]sx?$/i +export const NODE_MODULES = 'node_modules' +export const NODE_MODULES_REG = /(.*)node_modules/ + export const enum BUILD_TYPES { WEAPP = 'weapp', SWAN ='swan', diff --git a/packages/taro-mini-runner/src/utils/index.ts b/packages/taro-mini-runner/src/utils/index.ts index d839bfbdcba1..8e12acf4cf68 100644 --- a/packages/taro-mini-runner/src/utils/index.ts +++ b/packages/taro-mini-runner/src/utils/index.ts @@ -1,9 +1,10 @@ import * as path from 'path' import * as fs from 'fs-extra' +import * as resolvePath from 'resolve' import * as t from 'babel-types' -import { CONFIG_MAP, JS_EXT, TS_EXT } from './constants' +import { CONFIG_MAP, JS_EXT, TS_EXT, NODE_MODULES_REG } from './constants' import { IOption, IComponentObj } from './types' export function isNpmPkg (name: string): boolean { @@ -130,6 +131,7 @@ export function resolveScriptPath (p: string): string { export function buildUsingComponents ( filePath: string, + sourceDir: string, pathAlias: IOption, components: IComponentObj[], isComponent?: boolean @@ -142,6 +144,9 @@ export function buildUsingComponents ( } componentPath = resolveScriptPath(path.resolve(filePath, '..', componentPath as string)) if (fs.existsSync(componentPath)) { + if (/node_modules/.test(componentPath)) { + componentPath = componentPath.replace(NODE_MODULES_REG, path.join(sourceDir, 'npm')) + } componentPath = promoteRelativePath(path.relative(filePath, componentPath)) } else { componentPath = component.path @@ -154,3 +159,17 @@ export function buildUsingComponents ( usingComponents } : {}) } +const npmCached = {} +export function resolveNpmSync (pkgName: string, root): string | null { + try { + if (!npmCached[pkgName]) { + return resolvePath.sync(pkgName, { basedir: root }) + } + return npmCached[pkgName] + } catch (err) { + if (err.code === 'MODULE_NOT_FOUND') { + throw new Error(`包 ${pkgName} 未安装`) + } + return null + } +} diff --git a/packages/taro-mini-runner/yarn.lock b/packages/taro-mini-runner/yarn.lock index 6c5798ac3df4..262bd904eee0 100644 --- a/packages/taro-mini-runner/yarn.lock +++ b/packages/taro-mini-runner/yarn.lock @@ -4622,6 +4622,12 @@ resolve@1.x, resolve@^1.10.0, resolve@^1.3.2: dependencies: path-parse "^1.0.6" +resolve@^1.11.1: + version "1.11.1" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" + dependencies: + path-parse "^1.0.6" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"