build.ts 5.5 KB
Newer Older
fxy060608's avatar
fxy060608 已提交
1
import fs from 'fs'
fxy060608's avatar
fxy060608 已提交
2
import path from 'path'
fxy060608's avatar
fxy060608 已提交
3
import debug from 'debug'
fxy060608's avatar
fxy060608 已提交
4
import { BuildOptions, UserConfig } from 'vite'
fxy060608's avatar
fxy060608 已提交
5

fxy060608's avatar
fxy060608 已提交
6
import {
fxy060608's avatar
fxy060608 已提交
7
  emptyDir,
fxy060608's avatar
fxy060608 已提交
8
  EXTNAME_JS_RE,
fxy060608's avatar
fxy060608 已提交
9
  normalizePath,
fxy060608's avatar
fxy060608 已提交
10 11
  hasJsonFile,
  removeExt,
fxy060608's avatar
fxy060608 已提交
12
  resolveMainPathOnce,
13
  normalizeMiniProgramFilename,
fxy060608's avatar
fxy060608 已提交
14
  isCSSRequest,
fxy060608's avatar
fxy060608 已提交
15 16
  parseManifestJsonOnce,
  M,
fxy060608's avatar
fxy060608 已提交
17
} from '@dcloudio/uni-cli-shared'
fxy060608's avatar
fxy060608 已提交
18
import { GetManualChunk, GetModuleInfo, Plugin, PreRenderedChunk } from 'rollup'
fxy060608's avatar
fxy060608 已提交
19 20 21 22 23
import {
  isUniComponentUrl,
  isUniPageUrl,
  parseVirtualComponentPath,
  parseVirtualPagePath,
fxy060608's avatar
fxy060608 已提交
24
} from '../plugins/entry'
fxy060608's avatar
fxy060608 已提交
25

fxy060608's avatar
fxy060608 已提交
26 27
const debugChunk = debug('vite:uni:chunk')

fxy060608's avatar
fxy060608 已提交
28
export function buildOptions(): UserConfig['build'] {
fxy060608's avatar
fxy060608 已提交
29
  const platform = process.env.UNI_PLATFORM
fxy060608's avatar
fxy060608 已提交
30
  const inputDir = process.env.UNI_INPUT_DIR
fxy060608's avatar
fxy060608 已提交
31 32 33 34 35
  const outputDir = process.env.UNI_OUTPUT_DIR
  // 开始编译时,清空输出目录
  if (fs.existsSync(outputDir)) {
    emptyDir(outputDir)
  }
fxy060608's avatar
fxy060608 已提交
36 37 38 39 40 41 42
  return createBuildOptions(inputDir, platform)
}

export function createBuildOptions(
  inputDir: string,
  platform: UniApp.PLATFORM
): BuildOptions {
fxy060608's avatar
fxy060608 已提交
43 44
  return {
    // sourcemap: 'inline', // TODO
fxy060608's avatar
fxy060608 已提交
45
    // target: ['chrome53'], // 由小程序自己启用 es6 编译
fxy060608's avatar
fxy060608 已提交
46
    emptyOutDir: false, // 不清空输出目录,否则会影响自定义的一些文件输出,比如wxml
47 48 49 50 51 52
    lib: {
      // 必须使用 lib 模式,否则会生成 preload 等代码
      fileName: 'app.js',
      entry: resolveMainPathOnce(inputDir),
      formats: ['cjs'],
    },
fxy060608's avatar
fxy060608 已提交
53
    rollupOptions: {
fxy060608's avatar
fxy060608 已提交
54
      input: parseRollupInput(inputDir, platform),
fxy060608's avatar
fxy060608 已提交
55
      output: {
fxy060608's avatar
fxy060608 已提交
56 57 58 59 60 61
        entryFileNames(chunk) {
          if (chunk.name === 'main') {
            return 'app.js'
          }
          return chunk.name + '.js'
        },
fxy060608's avatar
fxy060608 已提交
62
        format: 'cjs',
fxy060608's avatar
fxy060608 已提交
63
        manualChunks: createMoveToVendorChunkFn(),
fxy060608's avatar
fxy060608 已提交
64
        chunkFileNames: createChunkFileNames(inputDir),
fxy060608's avatar
fxy060608 已提交
65
        assetFileNames: '[name][extname]',
fxy060608's avatar
fxy060608 已提交
66
        plugins: [dynamicImportPolyfill()],
fxy060608's avatar
fxy060608 已提交
67 68 69 70
      },
    },
  }
}
fxy060608's avatar
fxy060608 已提交
71

fxy060608's avatar
fxy060608 已提交
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
function parseRollupInput(inputDir: string, platform: UniApp.PLATFORM) {
  const inputOptions: Record<string, string> = {
    app: resolveMainPathOnce(inputDir),
  }
  if (process.env.UNI_MP_PLUGIN) {
    return inputOptions
  }
  const manifestJson = parseManifestJsonOnce(inputDir)
  const plugins = manifestJson[platform]?.plugins || {}
  Object.keys(plugins).forEach((name) => {
    const pluginExport = plugins[name].export
    if (!pluginExport) {
      return
    }
    const pluginExportFile = path.resolve(inputDir, pluginExport)
    if (!fs.existsSync(pluginExportFile)) {
      notFound(pluginExportFile)
    }
    inputOptions[removeExt(pluginExport)] = pluginExportFile
  })
  return inputOptions
}

fxy060608's avatar
fxy060608 已提交
95
function isVueJs(id: string) {
fxy060608's avatar
fxy060608 已提交
96
  return id.includes('plugin-vue:export-helper')
fxy060608's avatar
fxy060608 已提交
97 98 99 100
}

const chunkFileNameBlackList = ['main', 'pages.json', 'manifest.json']

fxy060608's avatar
fxy060608 已提交
101 102
function createMoveToVendorChunkFn(): GetManualChunk {
  const cache = new Map<string, boolean>()
fxy060608's avatar
fxy060608 已提交
103
  const inputDir = normalizePath(process.env.UNI_INPUT_DIR)
fxy060608's avatar
fxy060608 已提交
104
  return (id, { getModuleInfo }) => {
fxy060608's avatar
fxy060608 已提交
105
    id = normalizePath(id)
fxy060608's avatar
fxy060608 已提交
106 107 108
    const filename = id.split('?')[0]
    // 处理项目内的js,ts文件
    if (EXTNAME_JS_RE.test(filename)) {
109
      if (filename.startsWith(inputDir) && !filename.includes('node_modules')) {
fxy060608's avatar
fxy060608 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
        const chunkFileName = removeExt(
          normalizePath(path.relative(inputDir, filename))
        )
        if (
          !chunkFileNameBlackList.includes(chunkFileName) &&
          !hasJsonFile(chunkFileName) // 无同名的page,component
        ) {
          debugChunk(chunkFileName, id)
          return chunkFileName
        }
        return
      }
      // 非项目内的 js 资源,均打包到 vendor
      debugChunk('common/vendor', id)
      return 'common/vendor'
    }
fxy060608's avatar
fxy060608 已提交
126
    if (
fxy060608's avatar
fxy060608 已提交
127 128 129 130
      isVueJs(id) ||
      (id.includes('node_modules') &&
        !isCSSRequest(id) &&
        staticImportedByEntry(id, getModuleInfo, cache))
fxy060608's avatar
fxy060608 已提交
131
    ) {
fxy060608's avatar
fxy060608 已提交
132 133 134
      debugChunk('common/vendor', id)
      return 'common/vendor'
    }
fxy060608's avatar
fxy060608 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
  }
}

function staticImportedByEntry(
  id: string,
  getModuleInfo: GetModuleInfo,
  cache: Map<string, boolean>,
  importStack: string[] = []
): boolean {
  if (cache.has(id)) {
    return cache.get(id) as boolean
  }
  if (importStack.includes(id)) {
    // circular deps!
    cache.set(id, false)
    return false
  }
  const mod = getModuleInfo(id)
  if (!mod) {
    cache.set(id, false)
    return false
  }

  if (mod.isEntry) {
    cache.set(id, true)
    return true
  }
  const someImporterIs = mod.importers.some((importer) =>
    staticImportedByEntry(
      importer,
      getModuleInfo,
      cache,
      importStack.concat(id)
    )
  )
  cache.set(id, someImporterIs)
  return someImporterIs
}
fxy060608's avatar
fxy060608 已提交
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204

function createChunkFileNames(
  inputDir: string
): (chunkInfo: PreRenderedChunk) => string {
  return function chunkFileNames(chunk) {
    if (chunk.isDynamicEntry && chunk.facadeModuleId) {
      let id = chunk.facadeModuleId
      if (isUniPageUrl(id)) {
        id = path.resolve(process.env.UNI_INPUT_DIR, parseVirtualPagePath(id))
      } else if (isUniComponentUrl(id)) {
        id = path.resolve(
          process.env.UNI_INPUT_DIR,
          parseVirtualComponentPath(id)
        )
      }
      return removeExt(normalizeMiniProgramFilename(id, inputDir)) + '.js'
    }
    return '[name].js'
  }
}

function dynamicImportPolyfill(): Plugin {
  return {
    name: 'dynamic-import-polyfill',
    renderDynamicImport() {
      return {
        left: '(',
        right: ')',
      }
    },
  }
}
fxy060608's avatar
fxy060608 已提交
205 206 207 208 209 210 211

export function notFound(filename: string): never {
  console.log()
  console.error(M['file.notfound'].replace('{file}', filename))
  console.log()
  process.exit(0)
}