text: import NativeModule from 'module'
  import path from 'path'
  import qs from 'querystring'
  import LibraryTemplatePlugin from 'webpack/lib/LibraryTemplatePlugin'
  import ModuleReason from 'webpack/lib/ModuleReason'
  import NodeTargetPlugin from 'webpack/lib/node/NodeTargetPlugin'
  import NodeTemplatePlugin from 'webpack/lib/node/NodeTemplatePlugin'
  import LimitChunkCountPlugin from 'webpack/lib/optimize/LimitChunkCountPlugin'
  import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin'
  import ExternalDependency from './ExternalDependency'
  import VirtualDependency from './VirtualDependency'
  
  /**
   *
   * @param {any} loaderContext
   * @param {string} resource
   * @param {string} type
   * @return {{ loader: string, options: any }[]}
   */
  export function getMpflowLoaders(loaderContext, resource, type) {
    const ruleSets = loaderContext.__mpflowRuleSets || {}
  
    const resourceQuery = ''
    const resourcePath = resource
  
    if (!ruleSets[type]) return []
  
    const result = ruleSets[type].exec({
      resource: resourcePath,
      realResource: resourcePath,
      resourceQuery,
      issuer: loaderContext._module.issuer,
      compiler: loaderContext._compiler,
    })
  
    const useLoadersPost = []
    const useLoaders = []
    const useLoadersPre = []
  
    for (const r of result) {
      if (r.type === 'use') {
        if (r.enforce === 'post') {
          useLoadersPost.push(r.value)
        } else if (r.enforce === 'pre') {
          useLoadersPre.push(r.value)
        } else if (!r.enforce) {
          useLoaders.push(r.value)
        }
      }
    }
  
    const loaders = useLoadersPost.concat(useLoaders, useLoadersPre)
  
    return loaders
  }
  
  /**
   *
   * @param {string} resource
   * @param {{ loader: string, options: any }[]} loaders
   * @param {object} [options]
   * @param {'normal' | 'pre-normal' | 'all'} [options.disabled]
   */
  export function stringifyResource(resource, loaders, options = {}) {
    const { disabled } = options
    const segs = loaders.map(({ options, loader }) => {
      if (!options) return loader
      return `${loader}?${typeof options === 'object' ? qs.stringify(options) : options}`
    })
    segs.push(resource)
  
    let prefix = ''
    if (disabled === 'normal') {
      prefix = '!'
    } else if (disabled === 'pre-normal') {
      prefix = '-!'
    } else if (disabled === 'all') {
      prefix = '!!'
    }
  
    return `${prefix}${segs.join('!')}`
  }
  
  const MODULE_EXTERNAL_SYMBOL = Symbol('Module External')
  
  /**
   * 检查一个 entryPoint 是否为小程序入口
   * @param {*} entryPoint
   * @return {{ type: string, outputPath: string } | undefined}
   */
  export function isExternalEntryPoint(entryPoint) {
    if (!entryPoint || !entryPoint.chunks) return false
    for (const chunk of entryPoint.chunks) {
      const externalInfo = isExternalChunk(chunk)
      if (externalInfo) return externalInfo
    }
    return false
  }
  
  /**
   * 检查一个 chunk 是否为小程序入口
   * @param {*} chunk
   * @return {{ type: string, outputPath: string } | undefined}
   */
  export function isExternalChunk(chunk) {
    if (!chunk || !chunk.entryModule) return false
    for (const module of Array.from(chunk.modulesIterable)) {
      if (module && module.reasons) {
        for (const reason of module.reasons) {
          const dependency = reason.dependency
          if (dependency && dependency instanceof ExternalDependency) {
            return {
              type: dependency.externalType,
              outputPath: dependency.outputPath,
            }
          }
        }
      }
    }
    return false
  }
  
  /**
   * 标记当前的 chunk 为小程序入口
   * @param {*} module
   * @param {string} type
   * @param {string} outputPath
   */
  export function markAsExternal(module, type, outputPath) {
    const dependency = new ExternalDependency(module.request, type, outputPath)
    const reason = new ModuleReason(module, dependency, 'weflow markAsExternal')
    module.reasons.push(reason)
  }
  
  /**
   * 添加新的小程序入口
   * @param {*} loaderContext
   * @param {string} request
   * @param {string} type
   * @param {string} outputPath
   * @param {string} name
   */
  export async function addExternal(loaderContext, request, externalType, outputPath, name) {
    return new Promise((resolve, reject) => {
      const { _compilation: compilation, context } = loaderContext
      const dependency = new ExternalDependency(request, externalType, outputPath)
      compilation.addEntry(context, dependency, name || String(compilation._preparedEntrypoints.length), err =>
        err ? reject(err) : resolve(),
      )
    })
  }
  
  /**
   * 添加虚拟依赖
   * @param {*} loaderContext
   * @param {*} request
   */
  export function addDependency(loaderContext, request) {
    loaderContext.addDependency(request)
    loaderContext._module.addDependency(new VirtualDependency(request))
  }
  
  export function evalModuleCode(loaderContext, code, filename) {
    const module = new NativeModule(filename, loaderContext)
  
    module.paths = NativeModule._nodeModulePaths(loaderContext.context) // eslint-disable-line no-underscore-dangle
    module.filename = filename
    module._compile(code, filename) // eslint-disable-line no-underscore-dangle
  
    const exports = module.exports.__esModule ? module.exports.default : module.exports
  
    if (typeof exports === 'undefined') throw new Error(`Cannot get module exports from ${filename}`)
  
    return exports
  }
  
  // export function evalModuleBundleCode(loaderContext, code, filename, publicPath = '') {
  //   const moduleCache = new Map()
  //   const context = loaderContext.context
  //   const normalResolver = loaderContext._compiler.resolverFactory.get('normal')
  
  //   const loadModule = deasync(function (request, callback) {
  //     loaderContext.loadModule(request, (err, source) => {
  //       if (err) return callback(err)
  //       callback(null, source)
  //     })
  //   })
  
  //   const resolveRequest = deasync(function (request, callback) {
  //     const prefix = request.startsWith('-!')
  //       ? '-!'
  //       : request.startsWith('!!')
  //       ? '!!'
  //       : request.startsWith('!')
  //       ? '!'
  //       : ''
  //     const elements = request.replace(/^-?!+/, '').replace(/!!+/g, '!').split('!')
  //     const resource = elements.pop()
  
  //     normalResolver.resolve({}, context, resource, {}, (err, resolvedResource) => {
  //       if (err) return callback(err)
  //       callback(null, prefix + elements.concat([resolvedResource]).join('!'))
  //     })
  //   })
  
  //   function evalModule(src, filename) {
  //     const transpiled = babel.transform(src, {
  //       babelrc: false,
  //       presets: [
  //         [
  //           require('@babel/preset-env'),
  //           {
  //             modules: 'commonjs',
  //             targets: { node: 'current' },
  //           },
  //         ],
  //       ],
  //     })
  
  //     const script = new vm.Script(transpiled.code, {
  //       filename,
  //       displayErrors: true,
  //     })
  //     const moduleExports = { exports: {}, id: filename }
  //     const sandbox = Object.assign({}, global, {
  //       module: moduleExports,
  //       exports: moduleExports.exports,
  //       __webpack_public_path__: publicPath,
  //       require: requestPath => {
  //         const resolvedRequestPath = resolveRequest(requestPath)
  //         if (moduleCache.has(resolvedRequestPath)) return moduleCache.get(resolvedRequestPath).exports
  //         const requestContent = loadModule(resolvedRequestPath)
  //         const requestModule = evalModule(requestContent, resolvedRequestPath)
  //         return requestModule.exports
  //       },
  //     })
  //     script.runInNewContext(sandbox)
  
  //     moduleCache.set(filename, moduleExports)
  
  //     return moduleExports
  //   }
  
  //   return evalModule(code, filename)
  // }
  
  /**
   * 获取一个模块的 identifier
   * @param {*} compilation
   * @param {string} id
   */
  export function getModuleIdentifier(compilation, id) {
    const modules = compilation.modules
    for (const module of modules) {
      if (module.id === id) return module.identifier()
    }
    return null
  }
  
  /**
   * 执行获取一个 module 的实际导出内容
   * @param {*} loaderContext
   * @param {string} code
   * @param {string} filename
   * @param {string} [publicPath]
   */
  export async function evalModuleBundleCode(loaderContext, code, filename, publicPath = '') {
    const compilation = loaderContext._compilation
    // 除了自身之后的 loader 配置
    const loaders = loaderContext.loaders.slice(loaderContext.loaderIndex + 1)
    const resource = loaderContext.resource
    const context = loaderContext.context
  
    // 新建一个 childCompiler 对该文件进行处理
    const childFilename = '*'
    const outputOptions = { filename: childFilename, publicPath }
    const childCompiler = compilation.createChildCompiler(`eval module code ${resource}`, outputOptions)
  
    new NodeTemplatePlugin(outputOptions).apply(childCompiler)
    new LibraryTemplatePlugin(null, 'commonjs2').apply(childCompiler)
    new NodeTargetPlugin().apply(childCompiler)
    new SingleEntryPlugin(context, `!!${resource}`, resource).apply(childCompiler)
    new LimitChunkCountPlugin({ maxChunks: 1 }).apply(childCompiler)
  
    // 设置 loader
    childCompiler.hooks.thisCompilation.tap(`eval module code loader`, compilation => {
      compilation.hooks.normalModuleLoader.tap(`eval module code loader`, (context, module) => {
        context.emitFile = loaderContext.emitFile
  
        if (module.request === resource && loaders.length) {
          module.loaders = loaders.map(loader => {
            return {
              loader: loader.path,
              options: loader.options,
              ident: loader.ident,
            }
          })
        }
      })
    })
  
    let source
  
    // 截获 childCompiler 编译结果
    childCompiler.hooks.afterCompile.tap('eval module code', compilation => {
      if (compilation.compiler !== childCompiler) return
  
      source = compilation.assets[childFilename] && compilation.assets[childFilename].source()
  
      // Remove all chunk assets
      compilation.chunks.forEach(chunk => {
        chunk.files.forEach(file => {
          delete compilation.assets[file] // eslint-disable-line no-param-reassign
        })
      })
    })
  
    return await new Promise((resolve, reject) => {
      childCompiler.runAsChild((err, entries, compilation) => {
        if (err) return reject(err)
  
        if (compilation.errors.length > 0) return reject(compilation.errors[0])
  
        compilation.fileDependencies.forEach(dep => loaderContext.addDependency(dep))
  
        compilation.contextDependencies.forEach(dep => loaderContext.addContextDependency(dep))
  
        if (!source) return reject(new Error("Didn't get a result from child compiler"))
  
        const exports = evalModuleCode(loaderContext, source, resource)
  
        resolve({ exports, compilation })
      })
    })
  }
  
  /**
   * @param {import('webpack').loader.Loader} fn
   */
  export function asyncLoaderWrapper(fn) {
    return function (...args) {
      const callback = this.async()
      Promise.resolve(fn.apply(this, args)).then(
        res => (res ? callback(null, res) : callback()),
        err => callback(err),
      )
      return
    }
  }
  
  export function resolveWithType(loader, type, request) {
    const resolver = loader._compiler.resolverFactory.get(type)
    return new Promise((resolve, reject) => {
      resolver.resolve({}, loader.context, request, {}, (err, result) => (err ? reject(err) : resolve(result)))
    })
  }
  
  /**
   * 获取一个组件应该输出的路径位置
   * @param {string} rootContext webpack 编译的根路径，绝对路径
   * @param {string} appContext app 根目录相对 rootContext 的相对路径
   * @param {string} currentOutputPath 引用的组件的输出路径，相对 rootContext 的相对路径
   * @param {string} rawRequest 用户直接在 usingComponents 语句中使用的字符串
   * @param {string} pageRequest 解析后 page 真正所在的位置，绝对路径
   */
  export function getPageOutputPath(rootContext, appContext, currentOutputPath, rawRequest, pageRequest) {
    const relativePath = path.relative(
      path.resolve(rootContext, appContext),
      path.join(path.dirname(pageRequest), path.basename(pageRequest, path.extname(pageRequest))),
    )
  
    // 如果依然在 appContext 下，就保留目录结构
    if (!/^\.\.[\\/]/.test(relativePath)) return relativePath.replace(/^\.[\\/]/, '')
  
    // 不在 appContext 下，则放置到 miniprogram_npm 目录下
    return /^\./.test(rawRequest)
      ? path.join(path.dirname(currentOutputPath), rawRequest).replace(/^[\\/]/, '')
      : path.join('miniprogram_npm', rawRequest)
  }
  
  /**
   *
   * @param {*} loaderContext
   * @param {Buffer | string} source
   */
  export function getJSONContent(loaderContext, source) {
    if (typeof source !== 'string') source = source.toString('utf-8')
    if (source.trim().startsWith('{')) return JSON.parse(source)
    return loaderContext.exec(source, loaderContext.resourcePath)
  }
  
  /**
   * 判断一个 request 是否应该处理
   * @param {string} request
   * @return {boolean}
   */
  export function isRequest(request) {
    if (typeof request !== 'string' || !request) {
      return false
    }
  
    // 带有类似 plugin: 开头，并且不是 windows 的 path 如 C:\dir\file
    if (/^[a-z][a-z0-9+.-]*:/i.test(request) && !path.win32.isAbsolute(request)) {
      return false
    }
  
    return true
  }
  