提交 bd171238 编写于 作者: fxy060608's avatar fxy060608

wip(app): code spliting

上级 19607018
import { isFunction, isPromise } from '@vue/shared'
import { once, PageNodeOptions } from '@dcloudio/uni-shared'
import { DefineComponent } from 'vue'
import { createPageNode } from '../dom/Page'
......@@ -7,10 +8,21 @@ import { getVueApp } from '../app'
export type VuePageComponent = DefineComponent<PageProps>
type VuePageAsyncComponent = () => Promise<VuePageComponent>
function isVuePageAsyncComponent(
component: unknown
): component is VuePageAsyncComponent {
return isFunction(component)
}
const pagesMap = new Map<string, ReturnType<typeof createFactory>>()
export function definePage(pagePath: string, component: VuePageComponent) {
pagesMap.set(pagePath, once(createFactory(component)))
export function definePage(
pagePath: string,
asyncComponent: VuePageAsyncComponent | VuePageComponent
) {
pagesMap.set(pagePath, once(createFactory(asyncComponent)))
}
interface PageProps {
......@@ -29,20 +41,29 @@ export function createPage(
) {
const pageNode = createPageNode(__pageId, pageOptions, true)
const app = getVueApp()
return app.mountPage(
pagesMap.get(__pagePath)!(),
{
__pageId,
__pagePath,
__pageQuery,
__pageInstance,
},
pageNode
)
const component = pagesMap.get(__pagePath)!()
const mountPage = (component: VuePageComponent) =>
app.mountPage(
component,
{
__pageId,
__pagePath,
__pageQuery,
__pageInstance,
},
pageNode
)
if (isPromise(component)) {
return component.then((component) => mountPage(component))
}
return mountPage(component)
}
function createFactory(component: VuePageComponent) {
function createFactory(component: VuePageAsyncComponent | VuePageComponent) {
return () => {
if (isVuePageAsyncComponent(component)) {
return component().then((component) => setupPage(component))
}
return setupPage(component)
}
}
......@@ -11,14 +11,17 @@ const mainJs_1 = require("./plugins/mainJs");
const manifestJson_1 = require("./plugins/manifestJson");
const pagesJson_1 = require("./plugins/pagesJson");
const resolveId_1 = require("./plugins/resolveId");
exports.default = [
const plugins = [
resolveId_1.uniResolveIdPlugin(),
copy_1.uniCopyPlugin(),
mainJs_1.uniMainJsPlugin(),
manifestJson_1.uniManifestJsonPlugin(),
pagesJson_1.uniPagesJsonPlugin(),
plugin_1.UniAppPlugin,
uni_cli_shared_1.uniCssPlugin({
app: fs_1.default.readFileSync(require.resolve('@dcloudio/uni-app-plus/dist/style.css'), 'utf8'),
}),
];
if (!process.env.UNI_APP_CODE_SPLITING) {
plugins.push(uni_cli_shared_1.uniCssPlugin({
app: fs_1.default.readFileSync(require.resolve('@dcloudio/uni-app-plus/dist/style.css'), 'utf8'),
}));
}
exports.default = plugins;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UniAppPlugin = void 0;
const uni_shared_1 = require("@dcloudio/uni-shared");
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
exports.UniAppPlugin = {
name: 'vite:uni-app',
uni: {
compilerOptions: {
isNativeTag: uni_shared_1.isServiceNativeTag,
isCustomElement: uni_shared_1.isServiceCustomElement,
},
transformEvent: {
tap: 'click',
},
},
config() {
return {
build: {
rollupOptions: {
input: uni_cli_shared_1.resolveMainPathOnce(process.env.UNI_INPUT_DIR),
external: ['vue'],
output: {
name: 'AppService',
format: 'iife',
entryFileNames: 'app-service.js',
manualChunks: undefined,
globals: {
vue: 'Vue',
},
},
},
},
};
},
configResolved(config) {
const manifestJson = uni_cli_shared_1.parseManifestJsonOnce(process.env.UNI_INPUT_DIR);
if (uni_cli_shared_1.getNVueCompiler(manifestJson) === 'uni-app') {
process.env.UNI_USING_NVUE_COMPILER = 'uni-app';
}
if (uni_cli_shared_1.getNVueStyleCompiler(manifestJson) === 'uni-app') {
process.env.UNI_USING_NVUE_STYLE_COMPILER = 'uni-app';
}
// 移除 vite 内置的 css post 处理
const index = config.plugins.findIndex((p) => p.name === 'vite:css-post');
if (index > -1) {
;
config.plugins.splice(index, 1);
}
},
resolveId(id) {
if (id === 'vue') {
return uni_cli_shared_1.resolveBuiltIn('@dcloudio/uni-app-vue');
}
},
};
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildOptions = void 0;
const path_1 = __importDefault(require("path"));
const slash_1 = __importDefault(require("slash"));
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
exports.buildOptions = {
rollupOptions: {
input: uni_cli_shared_1.resolveMainPathOnce(process.env.UNI_INPUT_DIR),
external: ['vue'],
output: {
name: 'AppService',
format: process.env.UNI_APP_CODE_SPLITING ? 'amd' : 'iife',
entryFileNames: 'app-service.js',
manualChunks: {},
chunkFileNames(chunk) {
if (chunk.isDynamicEntry && chunk.facadeModuleId) {
const filepath = path_1.default.relative(process.env.UNI_INPUT_DIR, chunk.facadeModuleId);
return slash_1.default(filepath.replace(path_1.default.extname(filepath), '.js'));
}
return '[name].js';
},
assetFileNames: '[name][extname]',
globals: {
vue: 'Vue',
},
},
},
};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.configResolved = void 0;
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
const asset_1 = require("../plugins/vitejs/plugins/asset");
const css_1 = require("../plugins/vitejs/plugins/css");
const configResolved = (config) => {
const manifestJson = uni_cli_shared_1.parseManifestJsonOnce(process.env.UNI_INPUT_DIR);
if (uni_cli_shared_1.getNVueCompiler(manifestJson) === 'uni-app') {
process.env.UNI_USING_NVUE_COMPILER = 'uni-app';
}
if (uni_cli_shared_1.getNVueStyleCompiler(manifestJson) === 'uni-app') {
process.env.UNI_USING_NVUE_STYLE_COMPILER = 'uni-app';
}
if (process.env.UNI_APP_CODE_SPLITING) {
initCodeSpliting(config);
}
else {
// 移除 vite 内置的 css post 处理,交由 @dcloudio/uni-cli-shared 的 uniCssPlugin 实现
const index = config.plugins.findIndex((p) => p.name === 'vite:css-post');
if (index > -1) {
;
config.plugins.splice(index, 1);
}
}
};
exports.configResolved = configResolved;
function initCodeSpliting(config) {
// 替换内置插件
const replacedPlugins = [
asset_1.assetPlugin(config),
css_1.cssPlugin(config),
css_1.cssPostPlugin(config),
];
replacedPlugins.forEach((plugin) => {
const index = config.plugins.findIndex((p) => p.name === plugin.name);
if (index > -1) {
;
config.plugins.splice(index, 1, plugin);
}
});
const removedPlugins = ['vite:import-analysis'];
removedPlugins.forEach((name) => {
const index = config.plugins.findIndex((p) => p.name === name);
if (index > -1) {
;
config.plugins.splice(index, 1);
}
});
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UniAppPlugin = void 0;
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
const uni_1 = require("./uni");
const build_1 = require("./build");
const configResolved_1 = require("./configResolved");
exports.UniAppPlugin = {
name: 'vite:uni-app',
uni: uni_1.uniOptions,
config() {
return {
build: build_1.buildOptions,
};
},
configResolved: configResolved_1.configResolved,
resolveId(id) {
if (id === 'vue') {
return uni_cli_shared_1.resolveBuiltIn('@dcloudio/uni-app-vue');
}
},
};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.uniOptions = void 0;
const uni_shared_1 = require("@dcloudio/uni-shared");
exports.uniOptions = {
compilerOptions: {
isNativeTag: uni_shared_1.isServiceNativeTag,
isCustomElement: uni_shared_1.isServiceCustomElement,
},
transformEvent: {
tap: 'click',
},
};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CLIENT_PUBLIC_PATH = exports.FS_PREFIX = void 0;
/**
* https://github.com/vitejs/vite/blob/main/packages/vite/src/node/constants.ts
*/
/**
* Prefix for resolved fs paths, since windows paths may not be valid as URLs.
*/
exports.FS_PREFIX = `/@fs/`;
exports.CLIENT_PUBLIC_PATH = `/@vite/client`;
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.urlToBuiltUrl = exports.getAssetHash = exports.getAssetFilename = exports.fileToUrl = exports.checkPublicFile = exports.registerAssetToChunk = exports.assetPlugin = exports.chunkToEmittedAssetsMap = exports.assetUrlRE = void 0;
/**
* https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/asset.ts
*/
const path_1 = __importDefault(require("path"));
const url_1 = require("url");
const fs_1 = __importStar(require("fs"));
const lite_1 = __importDefault(require("mime/lite"));
const utils_1 = require("../utils");
const constants_1 = require("../constants");
const magic_string_1 = __importDefault(require("magic-string"));
const crypto_1 = require("crypto");
exports.assetUrlRE = /__VITE_ASSET__([a-z\d]{8})__(?:\$_(.*?)__)?/g;
// urls in JS must be quoted as strings, so when replacing them we need
// a different regex
const assetUrlQuotedRE = /"__VITE_ASSET__([a-z\d]{8})__(?:\$_(.*?)__)?"/g;
const rawRE = /(\?|&)raw(?:&|$)/;
const urlRE = /(\?|&)url(?:&|$)/;
exports.chunkToEmittedAssetsMap = new WeakMap();
const assetCache = new WeakMap();
const assetHashToFilenameMap = new WeakMap();
// save hashes of the files that has been emitted in build watch
const emittedHashMap = new WeakMap();
/**
* Also supports loading plain strings with import text from './foo.txt?raw'
*/
function assetPlugin(config) {
// assetHashToFilenameMap initialization in buildStart causes getAssetFilename to return undefined
assetHashToFilenameMap.set(config, new Map());
return {
name: 'vite:asset',
buildStart() {
assetCache.set(config, new Map());
emittedHashMap.set(config, new Set());
},
resolveId(id) {
if (!config.assetsInclude(utils_1.cleanUrl(id))) {
return;
}
// imports to absolute urls pointing to files in /public
// will fail to resolve in the main resolver. handle them here.
const publicFile = checkPublicFile(id, config);
if (publicFile) {
return id;
}
},
load(id) {
return __awaiter(this, void 0, void 0, function* () {
if (id.startsWith('\0')) {
// Rollup convention, this id should be handled by the
// plugin that marked it with \0
return;
}
// raw requests, read from disk
if (rawRE.test(id)) {
const file = checkPublicFile(id, config) || utils_1.cleanUrl(id);
// raw query, read file and return as string
return `export default ${JSON.stringify(yield fs_1.promises.readFile(file, 'utf-8'))}`;
}
if (!config.assetsInclude(utils_1.cleanUrl(id)) && !urlRE.test(id)) {
return;
}
id = id.replace(urlRE, '$1').replace(/[\?&]$/, '');
const url = yield fileToUrl(id, config, this);
return `export default ${JSON.stringify(url)}`;
});
},
renderChunk(code, chunk) {
let match;
let s;
while ((match = assetUrlQuotedRE.exec(code))) {
s = s || (s = new magic_string_1.default(code));
const [full, hash, postfix = ''] = match;
// some internal plugins may still need to emit chunks (e.g. worker) so
// fallback to this.getFileName for that.
const file = getAssetFilename(hash, config) || this.getFileName(hash);
registerAssetToChunk(chunk, file);
const outputFilepath = config.base + file + postfix;
s.overwrite(match.index, match.index + full.length, JSON.stringify(outputFilepath));
}
if (s) {
return {
code: s.toString(),
map: config.build.sourcemap ? s.generateMap({ hires: true }) : null,
};
}
else {
return null;
}
},
generateBundle(_, bundle) {
// do not emit assets for SSR build
if (config.command === 'build' && config.build.ssr) {
for (const file in bundle) {
if (bundle[file].type === 'asset' &&
!file.includes('ssr-manifest.json')) {
delete bundle[file];
}
}
}
},
};
}
exports.assetPlugin = assetPlugin;
function registerAssetToChunk(chunk, file) {
let emitted = exports.chunkToEmittedAssetsMap.get(chunk);
if (!emitted) {
emitted = new Set();
exports.chunkToEmittedAssetsMap.set(chunk, emitted);
}
emitted.add(utils_1.cleanUrl(file));
}
exports.registerAssetToChunk = registerAssetToChunk;
function checkPublicFile(url, { publicDir }) {
// note if the file is in /public, the resolver would have returned it
// as-is so it's not going to be a fully resolved path.
if (!publicDir || !url.startsWith('/')) {
return;
}
const publicFile = path_1.default.join(publicDir, utils_1.cleanUrl(url));
if (fs_1.default.existsSync(publicFile)) {
return publicFile;
}
else {
return;
}
}
exports.checkPublicFile = checkPublicFile;
function fileToUrl(id, config, ctx) {
if (config.command === 'serve') {
return fileToDevUrl(id, config);
}
else {
return fileToBuiltUrl(id, config, ctx);
}
}
exports.fileToUrl = fileToUrl;
function fileToDevUrl(id, config) {
let rtn;
if (checkPublicFile(id, config)) {
// in public dir, keep the url as-is
rtn = id;
}
else if (id.startsWith(config.root)) {
// in project root, infer short public path
rtn = '/' + path_1.default.posix.relative(config.root, id);
}
else {
// outside of project root, use absolute fs path
// (this is special handled by the serve static middleware
rtn = path_1.default.posix.join(constants_1.FS_PREFIX + id);
}
return config.base + rtn.replace(/^\//, '');
}
function getAssetFilename(hash, config) {
var _a;
return (_a = assetHashToFilenameMap.get(config)) === null || _a === void 0 ? void 0 : _a.get(hash);
}
exports.getAssetFilename = getAssetFilename;
/**
* Register an asset to be emitted as part of the bundle (if necessary)
* and returns the resolved public URL
*/
function fileToBuiltUrl(id, config, pluginContext, skipPublicCheck = false) {
return __awaiter(this, void 0, void 0, function* () {
if (!skipPublicCheck && checkPublicFile(id, config)) {
return config.base + id.slice(1);
}
const cache = assetCache.get(config);
const cached = cache.get(id);
if (cached) {
return cached;
}
const file = utils_1.cleanUrl(id);
const content = yield fs_1.promises.readFile(file);
let url;
if (config.build.lib ||
(!file.endsWith('.svg') &&
content.length < Number(config.build.assetsInlineLimit))) {
// base64 inlined as a string
url = `data:${lite_1.default.getType(file)};base64,${content.toString('base64')}`;
}
else {
// emit as asset
// rollup supports `import.meta.ROLLUP_FILE_URL_*`, but it generates code
// that uses runtime url sniffing and it can be verbose when targeting
// non-module format. It also fails to cascade the asset content change
// into the chunk's hash, so we have to do our own content hashing here.
// https://bundlers.tooling.report/hashing/asset-cascade/
// https://github.com/rollup/rollup/issues/3415
const map = assetHashToFilenameMap.get(config);
const contentHash = getAssetHash(content);
const { search, hash } = url_1.parse(id);
const postfix = (search || '') + (hash || '');
const basename = path_1.default.basename(file);
const ext = path_1.default.extname(basename);
const fileName = path_1.default.posix.join(config.build.assetsDir, `${basename.slice(0, -ext.length)}.${contentHash}${ext}`);
if (!map.has(contentHash)) {
map.set(contentHash, fileName);
}
const emittedSet = emittedHashMap.get(config);
if (!emittedSet.has(contentHash)) {
pluginContext.emitFile({
fileName,
type: 'asset',
source: content,
});
emittedSet.add(contentHash);
}
url = `__VITE_ASSET__${contentHash}__${postfix ? `$_${postfix}__` : ``}`;
}
cache.set(id, url);
return url;
});
}
function getAssetHash(content) {
return crypto_1.createHash('sha256').update(content).digest('hex').slice(0, 8);
}
exports.getAssetHash = getAssetHash;
function urlToBuiltUrl(url, importer, config, pluginContext) {
return __awaiter(this, void 0, void 0, function* () {
if (checkPublicFile(url, config)) {
return config.base + url.slice(1);
}
const file = url.startsWith('/')
? path_1.default.join(config.root, url)
: path_1.default.join(path_1.default.dirname(importer), url);
return fileToBuiltUrl(file, config, pluginContext,
// skip public check since we just did it above
true);
});
}
exports.urlToBuiltUrl = urlToBuiltUrl;
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.processSrcSet = exports.generateCodeFrame = exports.posToNumber = exports.pad = exports.isObject = exports.asyncReplace = exports.isDataUrl = exports.dataUrlRE = exports.isExternalUrl = exports.externalRE = exports.cleanUrl = exports.hashRE = exports.queryRE = exports.normalizePath = exports.isWindows = exports.deepImportRE = exports.bareImportRE = exports.slash = void 0;
/**
* https://github.com/vitejs/vite/blob/main/packages/vite/src/node/utils.ts
*/
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
function slash(p) {
return p.replace(/\\/g, '/');
}
exports.slash = slash;
exports.bareImportRE = /^[\w@](?!.*:\/\/)/;
exports.deepImportRE = /^([^@][^/]*)\/|^(@[^/]+\/[^/]+)\//;
exports.isWindows = os_1.default.platform() === 'win32';
function normalizePath(id) {
return path_1.default.posix.normalize(exports.isWindows ? slash(id) : id);
}
exports.normalizePath = normalizePath;
exports.queryRE = /\?.*$/s;
exports.hashRE = /#.*$/s;
const cleanUrl = (url) => url.replace(exports.hashRE, '').replace(exports.queryRE, '');
exports.cleanUrl = cleanUrl;
exports.externalRE = /^(https?:)?\/\//;
const isExternalUrl = (url) => exports.externalRE.test(url);
exports.isExternalUrl = isExternalUrl;
exports.dataUrlRE = /^\s*data:/i;
const isDataUrl = (url) => exports.dataUrlRE.test(url);
exports.isDataUrl = isDataUrl;
function asyncReplace(input, re, replacer) {
return __awaiter(this, void 0, void 0, function* () {
let match;
let remaining = input;
let rewritten = '';
while ((match = re.exec(remaining))) {
rewritten += remaining.slice(0, match.index);
rewritten += yield replacer(match);
remaining = remaining.slice(match.index + match[0].length);
}
rewritten += remaining;
return rewritten;
});
}
exports.asyncReplace = asyncReplace;
function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
exports.isObject = isObject;
const splitRE = /\r?\n/;
const range = 2;
function pad(source, n = 2) {
const lines = source.split(splitRE);
return lines.map((l) => ` `.repeat(n) + l).join(`\n`);
}
exports.pad = pad;
function posToNumber(source, pos) {
if (typeof pos === 'number')
return pos;
const lines = source.split(splitRE);
const { line, column } = pos;
let start = 0;
for (let i = 0; i < line - 1; i++) {
start += lines[i].length + 1;
}
return start + column;
}
exports.posToNumber = posToNumber;
function generateCodeFrame(source, start = 0, end) {
start = posToNumber(source, start);
end = end || start;
const lines = source.split(splitRE);
let count = 0;
const res = [];
for (let i = 0; i < lines.length; i++) {
count += lines[i].length + 1;
if (count >= start) {
for (let j = i - range; j <= i + range || end > count; j++) {
if (j < 0 || j >= lines.length)
continue;
const line = j + 1;
res.push(`${line}${' '.repeat(Math.max(3 - String(line).length, 0))}| ${lines[j]}`);
const lineLength = lines[j].length;
if (j === i) {
// push underline
const pad = start - (count - lineLength) + 1;
const length = Math.max(1, end > count ? lineLength - pad : end - start);
res.push(` | ` + ' '.repeat(pad) + '^'.repeat(length));
}
else if (j > i) {
if (end > count) {
const length = Math.max(Math.min(end - count, lineLength), 1);
res.push(` | ` + '^'.repeat(length));
}
count += lineLength + 1;
}
}
break;
}
}
return res.join('\n');
}
exports.generateCodeFrame = generateCodeFrame;
const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g;
function processSrcSet(srcs, replacer) {
return __awaiter(this, void 0, void 0, function* () {
const imageCandidates = srcs
.split(',')
.map((s) => {
const [url, descriptor] = s
.replace(escapedSpaceCharacters, ' ')
.trim()
.split(' ', 2);
return { url, descriptor };
})
.filter(({ url }) => !!url);
const ret = yield Promise.all(imageCandidates.map(({ url, descriptor }) => __awaiter(this, void 0, void 0, function* () {
return {
url: yield replacer({ url, descriptor }),
descriptor,
};
})));
const url = ret.reduce((prev, { url, descriptor }, index) => {
descriptor = descriptor || '';
return (prev +=
url + ` ${descriptor}${index === ret.length - 1 ? '' : ', '}`);
}, '');
return url;
});
}
exports.processSrcSet = processSrcSet;
此差异已折叠。
......@@ -23,5 +23,11 @@
"main": "dist/index.js"
},
"license": "Apache-2.0",
"gitHead": "56deaeb47d42e924d10282d7af418ccee6b139bf"
"gitHead": "56deaeb47d42e924d10282d7af418ccee6b139bf",
"dependencies": {
"clean-css": "^5.1.3",
"postcss-import": "^14.0.2",
"postcss-load-config": "^3.1.0",
"postcss-modules": "^4.1.3"
}
}
......@@ -7,17 +7,22 @@ import { uniManifestJsonPlugin } from './plugins/manifestJson'
import { uniPagesJsonPlugin } from './plugins/pagesJson'
import { uniResolveIdPlugin } from './plugins/resolveId'
export default [
const plugins = [
uniResolveIdPlugin(),
uniCopyPlugin(),
uniMainJsPlugin(),
uniManifestJsonPlugin(),
uniPagesJsonPlugin(),
UniAppPlugin,
uniCssPlugin({
app: fs.readFileSync(
require.resolve('@dcloudio/uni-app-plus/dist/style.css'),
'utf8'
),
}),
]
if (!process.env.UNI_APP_CODE_SPLITING) {
plugins.push(
uniCssPlugin({
app: fs.readFileSync(
require.resolve('@dcloudio/uni-app-plus/dist/style.css'),
'utf8'
),
})
)
}
export default plugins
import {
isServiceNativeTag,
isServiceCustomElement,
} from '@dcloudio/uni-shared'
import {
parseManifestJsonOnce,
resolveMainPathOnce,
UniVitePlugin,
getNVueCompiler,
getNVueStyleCompiler,
resolveBuiltIn,
} from '@dcloudio/uni-cli-shared'
export const UniAppPlugin: UniVitePlugin = {
name: 'vite:uni-app',
uni: {
compilerOptions: {
isNativeTag: isServiceNativeTag,
isCustomElement: isServiceCustomElement,
},
transformEvent: {
tap: 'click',
},
},
config() {
return {
build: {
rollupOptions: {
input: resolveMainPathOnce(process.env.UNI_INPUT_DIR),
external: ['vue'],
output: {
name: 'AppService',
format: 'iife',
entryFileNames: 'app-service.js',
manualChunks: undefined,
globals: {
vue: 'Vue',
},
},
},
},
}
},
configResolved(config) {
const manifestJson = parseManifestJsonOnce(process.env.UNI_INPUT_DIR)
if (getNVueCompiler(manifestJson) === 'uni-app') {
process.env.UNI_USING_NVUE_COMPILER = 'uni-app'
}
if (getNVueStyleCompiler(manifestJson) === 'uni-app') {
process.env.UNI_USING_NVUE_STYLE_COMPILER = 'uni-app'
}
// 移除 vite 内置的 css post 处理
const index = config.plugins.findIndex((p) => p.name === 'vite:css-post')
if (index > -1) {
;(config.plugins as Plugin[]).splice(index, 1)
}
},
resolveId(id) {
if (id === 'vue') {
return resolveBuiltIn('@dcloudio/uni-app-vue')
}
},
}
import path from 'path'
import slash from 'slash'
import { UserConfig } from 'vite'
import { resolveMainPathOnce } from '@dcloudio/uni-cli-shared'
export const buildOptions: UserConfig['build'] = {
rollupOptions: {
input: resolveMainPathOnce(process.env.UNI_INPUT_DIR),
external: ['vue'],
output: {
name: 'AppService',
format: process.env.UNI_APP_CODE_SPLITING ? 'amd' : 'iife',
entryFileNames: 'app-service.js',
manualChunks: {},
chunkFileNames(chunk) {
if (chunk.isDynamicEntry && chunk.facadeModuleId) {
const filepath = path.relative(
process.env.UNI_INPUT_DIR,
chunk.facadeModuleId
)
return slash(filepath.replace(path.extname(filepath), '.js'))
}
return '[name].js'
},
assetFileNames: '[name][extname]',
globals: {
vue: 'Vue',
},
},
},
}
import { Plugin, ResolvedConfig } from 'vite'
import {
parseManifestJsonOnce,
getNVueCompiler,
getNVueStyleCompiler,
} from '@dcloudio/uni-cli-shared'
import { assetPlugin } from '../plugins/vitejs/plugins/asset'
import { cssPlugin, cssPostPlugin } from '../plugins/vitejs/plugins/css'
export const configResolved: Plugin['configResolved'] = (config) => {
const manifestJson = parseManifestJsonOnce(process.env.UNI_INPUT_DIR)
if (getNVueCompiler(manifestJson) === 'uni-app') {
process.env.UNI_USING_NVUE_COMPILER = 'uni-app'
}
if (getNVueStyleCompiler(manifestJson) === 'uni-app') {
process.env.UNI_USING_NVUE_STYLE_COMPILER = 'uni-app'
}
if (process.env.UNI_APP_CODE_SPLITING) {
initCodeSpliting(config as ResolvedConfig)
} else {
// 移除 vite 内置的 css post 处理,交由 @dcloudio/uni-cli-shared 的 uniCssPlugin 实现
const index = config.plugins.findIndex((p) => p.name === 'vite:css-post')
if (index > -1) {
;(config.plugins as Plugin[]).splice(index, 1)
}
}
}
function initCodeSpliting(config: ResolvedConfig) {
// 替换内置插件
const replacedPlugins = [
assetPlugin(config),
cssPlugin(config),
cssPostPlugin(config),
]
replacedPlugins.forEach((plugin) => {
const index = (config.plugins as Plugin[]).findIndex(
(p) => p.name === plugin.name
)
if (index > -1) {
;(config.plugins as Plugin[]).splice(index, 1, plugin)
}
})
const removedPlugins = ['vite:import-analysis']
removedPlugins.forEach((name) => {
const index = config.plugins.findIndex((p) => p.name === name)
if (index > -1) {
;(config.plugins as Plugin[]).splice(index, 1)
}
})
}
import { UniVitePlugin, resolveBuiltIn } from '@dcloudio/uni-cli-shared'
import { uniOptions } from './uni'
import { buildOptions } from './build'
import { configResolved } from './configResolved'
export const UniAppPlugin: UniVitePlugin = {
name: 'vite:uni-app',
uni: uniOptions,
config() {
return {
build: buildOptions,
}
},
configResolved,
resolveId(id) {
if (id === 'vue') {
return resolveBuiltIn('@dcloudio/uni-app-vue')
}
},
}
import {
isServiceNativeTag,
isServiceCustomElement,
} from '@dcloudio/uni-shared'
import { UniVitePlugin } from '@dcloudio/uni-cli-shared'
export const uniOptions: UniVitePlugin['uni'] = {
compilerOptions: {
isNativeTag: isServiceNativeTag,
isCustomElement: isServiceCustomElement,
},
transformEvent: {
tap: 'click',
},
}
/**
* https://github.com/vitejs/vite/blob/main/packages/vite/src/node/constants.ts
*/
/**
* Prefix for resolved fs paths, since windows paths may not be valid as URLs.
*/
export const FS_PREFIX = `/@fs/`
export const CLIENT_PUBLIC_PATH = `/@vite/client`
/**
* https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/asset.ts
*/
import path from 'path'
import { parse as parseUrl } from 'url'
import fs, { promises as fsp } from 'fs'
import mime from 'mime/lite'
import { Plugin } from 'vite'
import { ResolvedConfig } from 'vite'
import { cleanUrl } from '../utils'
import { FS_PREFIX } from '../constants'
import { PluginContext, RenderedChunk } from 'rollup'
import MagicString from 'magic-string'
import { createHash } from 'crypto'
export const assetUrlRE = /__VITE_ASSET__([a-z\d]{8})__(?:\$_(.*?)__)?/g
// urls in JS must be quoted as strings, so when replacing them we need
// a different regex
const assetUrlQuotedRE = /"__VITE_ASSET__([a-z\d]{8})__(?:\$_(.*?)__)?"/g
const rawRE = /(\?|&)raw(?:&|$)/
const urlRE = /(\?|&)url(?:&|$)/
export const chunkToEmittedAssetsMap = new WeakMap<RenderedChunk, Set<string>>()
const assetCache = new WeakMap<ResolvedConfig, Map<string, string>>()
const assetHashToFilenameMap = new WeakMap<
ResolvedConfig,
Map<string, string>
>()
// save hashes of the files that has been emitted in build watch
const emittedHashMap = new WeakMap<ResolvedConfig, Set<string>>()
/**
* Also supports loading plain strings with import text from './foo.txt?raw'
*/
export function assetPlugin(config: ResolvedConfig): Plugin {
// assetHashToFilenameMap initialization in buildStart causes getAssetFilename to return undefined
assetHashToFilenameMap.set(config, new Map())
return {
name: 'vite:asset',
buildStart() {
assetCache.set(config, new Map())
emittedHashMap.set(config, new Set())
},
resolveId(id) {
if (!config.assetsInclude(cleanUrl(id))) {
return
}
// imports to absolute urls pointing to files in /public
// will fail to resolve in the main resolver. handle them here.
const publicFile = checkPublicFile(id, config)
if (publicFile) {
return id
}
},
async load(id) {
if (id.startsWith('\0')) {
// Rollup convention, this id should be handled by the
// plugin that marked it with \0
return
}
// raw requests, read from disk
if (rawRE.test(id)) {
const file = checkPublicFile(id, config) || cleanUrl(id)
// raw query, read file and return as string
return `export default ${JSON.stringify(
await fsp.readFile(file, 'utf-8')
)}`
}
if (!config.assetsInclude(cleanUrl(id)) && !urlRE.test(id)) {
return
}
id = id.replace(urlRE, '$1').replace(/[\?&]$/, '')
const url = await fileToUrl(id, config, this)
return `export default ${JSON.stringify(url)}`
},
renderChunk(code, chunk) {
let match: RegExpExecArray | null
let s: MagicString | undefined
while ((match = assetUrlQuotedRE.exec(code))) {
s = s || (s = new MagicString(code))
const [full, hash, postfix = ''] = match
// some internal plugins may still need to emit chunks (e.g. worker) so
// fallback to this.getFileName for that.
const file = getAssetFilename(hash, config) || this.getFileName(hash)
registerAssetToChunk(chunk, file)
const outputFilepath = config.base + file + postfix
s.overwrite(
match.index,
match.index + full.length,
JSON.stringify(outputFilepath)
)
}
if (s) {
return {
code: s.toString(),
map: config.build.sourcemap ? s.generateMap({ hires: true }) : null,
}
} else {
return null
}
},
generateBundle(_, bundle) {
// do not emit assets for SSR build
if (config.command === 'build' && config.build.ssr) {
for (const file in bundle) {
if (
bundle[file].type === 'asset' &&
!file.includes('ssr-manifest.json')
) {
delete bundle[file]
}
}
}
},
}
}
export function registerAssetToChunk(chunk: RenderedChunk, file: string): void {
let emitted = chunkToEmittedAssetsMap.get(chunk)
if (!emitted) {
emitted = new Set()
chunkToEmittedAssetsMap.set(chunk, emitted)
}
emitted.add(cleanUrl(file))
}
export function checkPublicFile(
url: string,
{ publicDir }: ResolvedConfig
): string | undefined {
// note if the file is in /public, the resolver would have returned it
// as-is so it's not going to be a fully resolved path.
if (!publicDir || !url.startsWith('/')) {
return
}
const publicFile = path.join(publicDir, cleanUrl(url))
if (fs.existsSync(publicFile)) {
return publicFile
} else {
return
}
}
export function fileToUrl(
id: string,
config: ResolvedConfig,
ctx: PluginContext
): string | Promise<string> {
if (config.command === 'serve') {
return fileToDevUrl(id, config)
} else {
return fileToBuiltUrl(id, config, ctx)
}
}
function fileToDevUrl(id: string, config: ResolvedConfig) {
let rtn: string
if (checkPublicFile(id, config)) {
// in public dir, keep the url as-is
rtn = id
} else if (id.startsWith(config.root)) {
// in project root, infer short public path
rtn = '/' + path.posix.relative(config.root, id)
} else {
// outside of project root, use absolute fs path
// (this is special handled by the serve static middleware
rtn = path.posix.join(FS_PREFIX + id)
}
return config.base + rtn.replace(/^\//, '')
}
export function getAssetFilename(
hash: string,
config: ResolvedConfig
): string | undefined {
return assetHashToFilenameMap.get(config)?.get(hash)
}
/**
* Register an asset to be emitted as part of the bundle (if necessary)
* and returns the resolved public URL
*/
async function fileToBuiltUrl(
id: string,
config: ResolvedConfig,
pluginContext: PluginContext,
skipPublicCheck = false
): Promise<string> {
if (!skipPublicCheck && checkPublicFile(id, config)) {
return config.base + id.slice(1)
}
const cache = assetCache.get(config)!
const cached = cache.get(id)
if (cached) {
return cached
}
const file = cleanUrl(id)
const content = await fsp.readFile(file)
let url: string
if (
config.build.lib ||
(!file.endsWith('.svg') &&
content.length < Number(config.build.assetsInlineLimit))
) {
// base64 inlined as a string
url = `data:${mime.getType(file)};base64,${content.toString('base64')}`
} else {
// emit as asset
// rollup supports `import.meta.ROLLUP_FILE_URL_*`, but it generates code
// that uses runtime url sniffing and it can be verbose when targeting
// non-module format. It also fails to cascade the asset content change
// into the chunk's hash, so we have to do our own content hashing here.
// https://bundlers.tooling.report/hashing/asset-cascade/
// https://github.com/rollup/rollup/issues/3415
const map = assetHashToFilenameMap.get(config)!
const contentHash = getAssetHash(content)
const { search, hash } = parseUrl(id)
const postfix = (search || '') + (hash || '')
const basename = path.basename(file)
const ext = path.extname(basename)
const fileName = path.posix.join(
config.build.assetsDir,
`${basename.slice(0, -ext.length)}.${contentHash}${ext}`
)
if (!map.has(contentHash)) {
map.set(contentHash, fileName)
}
const emittedSet = emittedHashMap.get(config)!
if (!emittedSet.has(contentHash)) {
pluginContext.emitFile({
fileName,
type: 'asset',
source: content,
})
emittedSet.add(contentHash)
}
url = `__VITE_ASSET__${contentHash}__${postfix ? `$_${postfix}__` : ``}`
}
cache.set(id, url)
return url
}
export function getAssetHash(content: Buffer): string {
return createHash('sha256').update(content).digest('hex').slice(0, 8)
}
export async function urlToBuiltUrl(
url: string,
importer: string,
config: ResolvedConfig,
pluginContext: PluginContext
): Promise<string> {
if (checkPublicFile(url, config)) {
return config.base + url.slice(1)
}
const file = url.startsWith('/')
? path.join(config.root, url)
: path.join(path.dirname(importer), url)
return fileToBuiltUrl(
file,
config,
pluginContext,
// skip public check since we just did it above
true
)
}
此差异已折叠。
此差异已折叠。
......@@ -3,5 +3,10 @@
"compilerOptions": {
"outDir": "dist"
},
"include": ["src", "../shims-node.d.ts", "../shims-uni-app.d.ts"]
"include": [
"src",
"types/shims.d.ts",
"../shims-node.d.ts",
"../shims-uni-app.d.ts"
]
}
/**
Types from https://github.com/rollup/plugins/blob/master/packages/alias/types/index.d.ts
Inlined because the plugin is bundled.
https://github.com/rollup/plugins/blob/master/LICENSE
The MIT License (MIT)
Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { PluginHooks } from 'rollup'
export interface Alias {
find: string | RegExp
replacement: string
/**
* Instructs the plugin to use an alternative resolving algorithm,
* rather than the Rollup's resolver.
* @default null
*/
customResolver?: ResolverFunction | ResolverObject | null
}
export type ResolverFunction = PluginHooks['resolveId']
export interface ResolverObject {
buildStart?: PluginHooks['buildStart']
resolveId: ResolverFunction
}
/**
* Specifies an `Object`, or an `Array` of `Object`,
* which defines aliases used to replace values in `import` or `require` statements.
* With either format, the order of the entries is important,
* in that the first defined rules are applied first.
*
* This is passed to \@rollup/plugin-alias as the "entries" field
* https://github.com/rollup/plugins/tree/master/packages/alias#entries
*/
export type AliasOptions = readonly Alias[] | { [find: string]: string }
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册