mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 02:54:11 +08:00
242 lines
7.7 KiB
TypeScript
242 lines
7.7 KiB
TypeScript
import { parse as parseFile, join, basename, relative } from 'path';
|
|
import * as minimatch from 'minimatch';
|
|
|
|
export interface PlatformFSPluginOptions {
|
|
/**
|
|
* The target platform.
|
|
*/
|
|
platform?: string;
|
|
|
|
/**
|
|
* A list of all platforms. By default it is `["ios", "android", "desktop"]`.
|
|
*/
|
|
platforms?: string[];
|
|
|
|
/**
|
|
* An array of minimatch expressions used to filter nodes from the file system.
|
|
*/
|
|
ignore?: string[];
|
|
}
|
|
|
|
const internalPlatforms = ['ios', 'android'];
|
|
|
|
export class PlatformFSPlugin {
|
|
protected readonly platform: string;
|
|
protected readonly platforms: ReadonlyArray<string>;
|
|
protected readonly ignore: ReadonlyArray<string>;
|
|
protected context: string;
|
|
|
|
constructor({ platform, platforms, ignore }: PlatformFSPluginOptions) {
|
|
this.platform = platform || '';
|
|
this.platforms = platforms || internalPlatforms;
|
|
this.ignore = ignore || [];
|
|
}
|
|
|
|
public apply(compiler) {
|
|
this.context = compiler.context;
|
|
compiler.inputFileSystem = mapFileSystem({
|
|
platform: this.platform,
|
|
platforms: this.platforms,
|
|
ignore: this.ignore,
|
|
context: this.context,
|
|
compiler,
|
|
});
|
|
}
|
|
}
|
|
|
|
export interface MapFileSystemArgs {
|
|
/**
|
|
* This is the underlying webpack compiler.inputFileSystem, its interface is similar to Node's fs.
|
|
*/
|
|
readonly context: string;
|
|
readonly platform: string;
|
|
readonly platforms: ReadonlyArray<string>;
|
|
readonly ignore: ReadonlyArray<string>;
|
|
readonly compiler: any;
|
|
}
|
|
|
|
export function mapFileSystem(args: MapFileSystemArgs): any {
|
|
let { platform, platforms, ignore, context, compiler } = args;
|
|
const fs = compiler.inputFileSystem;
|
|
ignore = args.ignore || [];
|
|
|
|
const isExternal = internalPlatforms.indexOf(platform) === -1;
|
|
|
|
const minimatchFileFilters = ignore.map((pattern) => {
|
|
const minimatchFilter = minimatch.filter(pattern);
|
|
return (file) => minimatchFilter(relative(context, file), 0, []);
|
|
});
|
|
|
|
const isIgnored = (file) => minimatchFileFilters.some((filter) => filter(file));
|
|
|
|
const alienPlatforms = platforms.filter((p) => p !== platform);
|
|
const alienPlatformFilters = alienPlatforms.map((platform) => `.${platform}.`).map((contains) => (baseFileName) => baseFileName.indexOf(contains) !== -1);
|
|
|
|
const isNotAlienPlatformFile = (file) => !alienPlatformFilters.some((filter) => filter(basename(file)));
|
|
|
|
const currentPlatformExt = `.${platform}`;
|
|
|
|
const trimPlatformSuffix = (file) => {
|
|
const { dir, name, ext } = parseFile(file);
|
|
if (name.endsWith(currentPlatformExt)) {
|
|
return join(dir, name.substr(0, name.length - currentPlatformExt.length) + ext);
|
|
}
|
|
return file;
|
|
};
|
|
|
|
const isNotIgnored = (file) => !isIgnored(file);
|
|
|
|
const mappedFS: any = {
|
|
get _statStorage() {
|
|
return fs._statStorage;
|
|
},
|
|
get _readFileStorage() {
|
|
return fs._readFileStorage;
|
|
},
|
|
get _readdirStorage() {
|
|
return fs._readdirStorage;
|
|
},
|
|
};
|
|
|
|
['readFile', 'provide', 'stat', 'readJson', 'readlink'].forEach(mapPath);
|
|
['readdir'].forEach(filterResultingFiles);
|
|
|
|
function platformSpecificFile(file: string): string {
|
|
const { dir, name, ext } = parseFile(file);
|
|
let platformFilePath = join(dir, `${name}.${platform}${ext}`);
|
|
|
|
if (isExternal && dir.indexOf('/@nativescript/core/') !== -1) {
|
|
let replacedPath;
|
|
try {
|
|
replacedPath = dir.replace(/node_modules(\/[^/]+)?\/@nativescript\/core/, `node_modules/nativescript-platform-${platform}`);
|
|
|
|
platformFilePath = require.resolve(join(replacedPath, `${name}.${platform}${ext}`));
|
|
} catch (e) {
|
|
if (replacedPath) {
|
|
if (ext === '.d') {
|
|
platformFilePath = undefined;
|
|
} else {
|
|
platformFilePath = join(replacedPath, `${name}${ext}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return platformFilePath;
|
|
}
|
|
|
|
function listWithPlatformSpecificFiles(files: string[]): string[] {
|
|
const mappedFiles = [];
|
|
files.forEach((file) => {
|
|
mappedFiles.push(file);
|
|
mappedFiles.push(platformSpecificFile(file));
|
|
});
|
|
return mappedFiles;
|
|
}
|
|
|
|
/**
|
|
* Maps and filters the input files.
|
|
* Expects array with absolute paths.
|
|
* Returns array with absolute paths.
|
|
*/
|
|
function filterIgnoredFilesAlienFilesAndMap(files: string[]): string[] {
|
|
const mappedFiles = files.filter(isNotIgnored).filter(isNotAlienPlatformFile).map(trimPlatformSuffix);
|
|
const uniqueMappedFiles = Array.from(new Set(mappedFiles));
|
|
return uniqueMappedFiles;
|
|
}
|
|
|
|
const platformSuffix = '.' + platform + '.';
|
|
const baseWatch = compiler.watchFileSystem.watch;
|
|
compiler.watchFileSystem.watch = function (files, dirs, missing, startTime, watchOptions, callback: (err, filesModified, contextModified, missingModified, fileTimestamps, contextTimestamps) => void, callbackUndelayed: (filename, timestamp) => void) {
|
|
const mappedFiles = listWithPlatformSpecificFiles(files);
|
|
|
|
const newCallback = function (err, filesModified, contextModified, missingModified, fileTimestamps, contextTimestamps) {
|
|
const mappedFilesModified = filterIgnoredFilesAlienFilesAndMap(filesModified);
|
|
const mappedTimestamps = new Map();
|
|
const fileTimestampsAsArray = Array.from(fileTimestamps);
|
|
|
|
for (const entry of fileTimestampsAsArray) {
|
|
const file = entry[0];
|
|
const timestamp = entry[1];
|
|
mappedTimestamps.set(file, timestamp);
|
|
|
|
const platformSuffixIndex = file.lastIndexOf(platformSuffix);
|
|
if (platformSuffixIndex != -1) {
|
|
// file name without platform suffix
|
|
const mappedFile = file.substr(0, platformSuffixIndex) + file.substr(platformSuffixIndex + platformSuffix.length - 1);
|
|
if (!(mappedFile in fileTimestamps)) {
|
|
mappedTimestamps.set(mappedFile, timestamp);
|
|
}
|
|
}
|
|
}
|
|
|
|
callback.call(this, err, mappedFilesModified, contextModified, missingModified, mappedTimestamps, contextTimestamps);
|
|
};
|
|
|
|
const newCallbackUndelayed = function (filename, timestamp) {
|
|
compiler.watchFileSystem.inputFileSystem.purge(filename);
|
|
callbackUndelayed(filename, timestamp);
|
|
};
|
|
|
|
baseWatch.apply(compiler.watchFileSystem.watch, [mappedFiles, dirs, missing, startTime, watchOptions, newCallback, newCallbackUndelayed]);
|
|
};
|
|
|
|
/**
|
|
* For FS functions that get as first argument a file path,
|
|
* this will map it to a platform specific file if such file exists or fallback to the default.
|
|
* Also the last argument must be a function that handles results such as (err, files[]),
|
|
* it will invoke err for files that are ignored.
|
|
*/
|
|
function mapPath(fName) {
|
|
const base = fs[fName];
|
|
mappedFS[fName] = function () {
|
|
const args = arguments;
|
|
const originalFilePath = args[0];
|
|
const callback = args[args.length - 1];
|
|
if (isIgnored(originalFilePath)) {
|
|
callback(new Error('File ' + originalFilePath + ' is ignored!'));
|
|
return;
|
|
}
|
|
const platformFilePath = platformSpecificFile(originalFilePath);
|
|
fs.stat(platformFilePath, (err, stat) => {
|
|
if (!err && stat && stat.isFile()) {
|
|
args[0] = platformFilePath;
|
|
}
|
|
base.apply(fs, args);
|
|
});
|
|
};
|
|
}
|
|
|
|
/**
|
|
* For FS functions that get as a last argument a function,
|
|
* that handles results such as (err, files[]),
|
|
* will filter and map the returned files[].
|
|
*/
|
|
function filterResultingFiles(name) {
|
|
const base = fs[name];
|
|
mappedFS[name] = function () {
|
|
const dir = arguments[0];
|
|
const callback = arguments[arguments.length - 1];
|
|
if (isIgnored(dir)) {
|
|
// Return empty file list for filtered directories.
|
|
callback(null, []);
|
|
return;
|
|
}
|
|
arguments[arguments.length - 1] = function (err, files: string[]) {
|
|
if (err) {
|
|
callback(err);
|
|
} else {
|
|
// Create absolute paths for "ignored" testing, map platforms, and return back the base name.
|
|
const absoluteFilePaths = files.map((file) => join(dir, file));
|
|
const resultAbsolute = filterIgnoredFilesAlienFilesAndMap(absoluteFilePaths);
|
|
const resultFileNames = resultAbsolute.map((f) => basename(f));
|
|
callback(null, resultFileNames);
|
|
}
|
|
};
|
|
base.apply(fs, arguments);
|
|
};
|
|
}
|
|
|
|
return mappedFS;
|
|
}
|