Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions packages/jsx-compiler/src/modules/code.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const t = require('@babel/types');
const { join, relative, dirname, resolve, extname } = require('path');
const { join, relative, dirname, resolve, extname, sep } = require('path');
const resolveModule = require('resolve');
const { parseExpression } = require('../parser');
const isClassComponent = require('../utils/isClassComponent');
Expand Down Expand Up @@ -67,7 +67,7 @@ function getConstructor(type) {
*/
module.exports = {
parse(parsed, code, options) {
const { ast, programPath, defaultExportedPath, exportComponentPath, renderFunctionPath,
const {ast, programPath, defaultExportedPath, exportComponentPath, renderFunctionPath,
useCreateStyle, useClassnames, dynamicValue, dynamicRef, dynamicStyle, dynamicEvents, imported,
contextList, refs, componentDependentProps, listKeyProps, renderItemFunctions, renderPropsFunctions, renderPropsEmitter, renderPropsListener, eventHandler, eventHandlers = [] } = parsed;
const { platform, type, cwd, outputPath, sourcePath, resourcePath, disableCopyNpm, virtualHost } = options;
Expand Down Expand Up @@ -326,23 +326,33 @@ function renameNpmModules(ast, targetFileDir, outputPath, cwd, resourcePath) {
// In tnpm, target will be like following (symbol linked path):
// ***/_universal-toast_1.0.0_universal-toast/lib/index.js
let packageJSONPath;
let packagePath;
try {
packageJSONPath = require.resolve(join(npmName, 'package.json'), { paths: searchPaths });
packagePath = packageJSONPath.replace('package.json', '');
} catch (err) {
throw new Error(`You may not have npm installed: "${npmName}"`);
}

const rootNodeModulePath = getRootNodeModulePath(rootContext, target);

// Hard link case if the package is not installed in current package node_modules
const isHardLink = rootNodeModulePath.indexOf(packagePath) === -1;

const moduleBasePath = join(packageJSONPath, '..');
const realNpmName = relative(nodeModulePath, moduleBasePath);
// Hard link pkg use fake npmName to enable npm folder copy behavior.
const realNpmName = isHardLink ? npmName : relative(rootNodeModulePath, moduleBasePath);
const modulePathSuffix = relative(moduleBasePath, target);

let ret;
// For disableCopyNpm=false scenario,all paths here are predicted paths,while the actual package copy behaivor is executed in jsx2mp-loader/src.script-loader.
if (npmName === value) {
ret = relative(targetFileDir, join(outputPath, 'npm', realNpmName, modulePathSuffix));
} else {
ret = relative(targetFileDir, join(outputPath, 'npm', value.replace(npmName, realNpmName)));
}
ret = addRelativePathPrefix(normalizeOutputFilePath(ret));

// ret => '../npm/_ali/universal-toast/lib/index.js

return t.stringLiteral(normalizeFileName(ret));
Expand Down Expand Up @@ -661,3 +671,33 @@ function addClearKeyCache(renderFunctionPath) {
)
);
}

function getRootNodeModulePath(root, current) {
const relativePathArray = relative(root, current).split(sep) || [];

if (relativePathArray.find((item) => item === '..')) {
/**
* Package hoist case exist while `..` is presented in relative path array
*/
const resourcePathArray = current.split('node_modules') || [];

if (resourcePathArray.length === 1) {
/**
* current file is not in node_modules folder, whiche means that the current file is in a mono package, so we need to use the root node_modules folder
*/
return join(root, 'node_modules');
} else {
/**
* If relative path array length is greater than 1, it means that the current file is in a nested node_modules folder, so we need to dig into the deepest node_modules folder
*/
return join(
resourcePathArray
.slice(0, resourcePathArray.length - 1)
.join('node_modules'),
'node_modules'
);
}
} else {
return join(root, 'node_modules');
}
}
20 changes: 18 additions & 2 deletions packages/jsx-compiler/src/modules/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,20 @@ function getComponentPath(alias, options) {
} else {
const { disableCopyNpm } = options;
const realNpmFile = resolveModule.sync(alias.from, { basedir: dirname(options.resourcePath), preserveSymlinks: false });

const pkgName = getNpmName(alias.from);
const realPkgName = getRealNpmPkgName(realNpmFile, pkgName);

// Package name won't be present in hard link case of realNpmFile
const isHardLink = realNpmFile.indexOf(pkgName) === -1;
// Use fake pkgName as real pkgName for hard link case to generate correct relative path in npm folder
const realPkgName = isHardLink ? pkgName : getRealNpmPkgName(realNpmFile, pkgName);

let hardLinkPkgPath = '';
if (isHardLink) {
const hardLinkPkgJSONPath = resolveModule.sync(`${alias.from}/package.json`, { basedir: dirname(options.resourcePath), preserveSymlinks: false });
hardLinkPkgPath = hardLinkPkgJSONPath.replace(/package\.json$/, '');
}

const targetFileDir = dirname(join(options.outputPath, relative(options.sourcePath, options.resourcePath)));
const npmRelativePath = relative(targetFileDir, join(options.outputPath, 'npm'));

Expand Down Expand Up @@ -413,7 +425,11 @@ function getComponentPath(alias, options) {

const miniappConfigRelativePath = relative(pkg.main, miniappComponentPath);
const realMiniappAbsPath = resolve(realNpmFile, miniappConfigRelativePath);
const realMiniappRelativePath = realMiniappAbsPath.slice(realMiniappAbsPath.indexOf(realPkgName) + realPkgName.length);

// there might be multiple realPkgName in realMiniappAbsPath,so we choose the last one as the relative path for npm folder
const realMiniAppAbsPathArray = realMiniappAbsPath.split(realPkgName) || [];
const realMiniappRelativePath = isHardLink ? relative(hardLinkPkgPath, realMiniappAbsPath) : (realMiniAppAbsPathArray[realMiniAppAbsPathArray.length - 1] || '');

return normalizeFileName(addRelativePathPrefix(normalizeOutputFilePath(join(npmRelativePath, realPkgName, realMiniappRelativePath))));
}
}
Expand Down
21 changes: 19 additions & 2 deletions packages/jsx2mp-loader/src/babel-plugin-rename-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const { constants: { QUICKAPP }} = require('miniapp-builder-shared');
const { isNpmModule, isWeexModule, isQuickAppModule, isRaxModule, isRaxAppModule, isJsx2mpRuntimeModule, isNodeNativeModule } = require('./utils/judgeModule');
const { addRelativePathPrefix, normalizeOutputFilePath, removeExt } = require('./utils/pathHelper');
const getAliasCorrespondingValue = require('./utils/getAliasCorrespondingValue');
const getRootNodeModulePath = require('./utils/getRootNodeModulePath');
const resolveModule = require('resolve');

const RUNTIME = 'jsx2mp-runtime';

Expand Down Expand Up @@ -34,8 +36,23 @@ module.exports = function visitor({ types: t }, options) {

const target = enhancedResolve.sync(resourcePath, value);

const rootNodeModulePath = join(rootContext, 'node_modules');
const filePath = relative(dirname(distSourcePath), join(outputPath, 'npm', relative(rootNodeModulePath, target)));
const rootNodeModulePath = getRootNodeModulePath(rootContext, target);

// Hard link case if the package is not installed in current package node_modules
const isHardLink = target.indexOf(rootNodeModulePath) === -1;

let hardLinkPkgPath;
if (isHardLink) {
try {
const hardLinkPkgJSONPath = resolveModule.sync(`${value}/package.json`, { basedir: dirname(resourcePath), paths: rootNodeModulePath, preserveSymlinks: false });
hardLinkPkgPath = hardLinkPkgJSONPath.replace(/package\.json$/, '');
} catch (e) {
console.error(e);
console.warn(chalk.yellow(`Can not find package.json of ${value} in ${resourcePath}`));
}
}

const filePath = relative(dirname(distSourcePath), join(outputPath, 'npm', isHardLink ? join(value, relative(hardLinkPkgPath, target)) : relative(rootNodeModulePath, target)));
let modifiedValue = normalizeNpmFileName(addRelativePathPrefix(normalizeOutputFilePath(filePath)));
// json file will be transformed to js file
if (extname(value) === '.json') {
Expand Down
53 changes: 43 additions & 10 deletions packages/jsx2mp-loader/src/script-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { isNpmModule, isJSONFile, isTypescriptFile } = require('./utils/judgeModu
const isMiniappComponent = require('./utils/isMiniappComponent');
const parse = require('./utils/parseRequest');
const { output, transformCode } = require('./output');
const getRootNodeModulePath = require('./utils/getRootNodeModulePath');

const ScriptLoader = __filename;

Expand All @@ -35,12 +36,19 @@ module.exports = function scriptLoader(content) {
const isCommonJSON = isJSON && !isAppJSon;

const rawContent = isCommonJSON ? content : readFileSync(this.resourcePath, 'utf-8');

const nodeModulesPathList = getNearestNodeModulesPath(rootContext, this.resourcePath);
const currentNodeModulePath = nodeModulesPathList[nodeModulesPathList.length - 1];
const rootNodeModulePath = join(rootContext, 'node_modules');
const rootNodeModulePath = getRootNodeModulePath(rootContext, this.resourcePath);

// Only remove last node_modules to get current package path
const currentPackagePath = currentNodeModulePath.split('node_modules').filter((item) => !!item).join('node_modules');

// Hard link case if the current package is not in root package folder
const isHardLink = rootNodeModulePath.indexOf(currentPackagePath) === -1;

const isFromNodeModule = cached(function isFromNodeModule(path) {
return path.indexOf(rootNodeModulePath) === 0;
return path.indexOf(rootNodeModulePath) === 0 || isHardLink;
});

const isFromConstantDir = cached(isFromTargetDirs(constantDir));
Expand All @@ -53,11 +61,24 @@ module.exports = function scriptLoader(content) {
const outputFile = (rawContent, isFromNpm = true) => {
let distSourcePath;
if (isFromNpm) {
const relativeNpmPath = relative(currentNodeModulePath, this.resourcePath);

const pkgPath = isHardLink ? currentPackagePath : currentNodeModulePath;

const pkgJsonPath = join(pkgPath, 'package.json');

let pkgJSON = '';
if (existsSync(pkgJsonPath)) {
pkgJSON = readJSONSync(pkgJsonPath);
}

const relativeNpmPath = relative(pkgPath, this.resourcePath);
const splitedNpmPath = relativeNpmPath.split(sep);
if (/^_?@/.test(relativeNpmPath)) splitedNpmPath.shift(); // Extra shift for scoped npm.
splitedNpmPath.shift(); // Skip npm module package, for cnpm/tnpm will rewrite this.
distSourcePath = normalizeNpmFileName(join(outputPath, 'npm', relative(rootNodeModulePath, this.resourcePath)));

const relativePathInNpmFolder = isHardLink && pkgJSON.name ? join(pkgJSON.name, relative(pkgPath, this.resourcePath)) : relative(rootNodeModulePath, this.resourcePath);

distSourcePath = normalizeNpmFileName(join(outputPath, 'npm', relativePathInNpmFolder));
} else {
const relativeFilePath = relative(
join(rootContext, dirname(entryPath)),
Expand Down Expand Up @@ -103,7 +124,7 @@ module.exports = function scriptLoader(content) {
overwrite: false,
filter: filename => {
const isJSONFile = extname(filename) === '.json';
const isNpmDirFile = filename.indexOf('npm') > -1;
const isNpmDirFile = filename.indexOf('/npm/') > -1;
// if isThirdMiniappComponent, only exclude the json file of the component itself
const filterJSONFile = isThirdMiniappComponent ? isNpmDirFile || !isJSONFile : !isJSONFile;
return !/__(mocks|tests?)__/.test(filename) && filterJSONFile; // JSON file will be written later because usingComponents may be modified
Expand All @@ -117,16 +138,23 @@ module.exports = function scriptLoader(content) {
if (platform.type === QUICKAPP) {
return;
}

if (existsSync(originalComponentConfigPath)) {
const componentConfig = readJSONSync(originalComponentConfigPath);
if (componentConfig.usingComponents) {
for (let key in componentConfig.usingComponents) {
if (componentConfig.usingComponents.hasOwnProperty(key)) {
const componentPath = componentConfig.usingComponents[key];
if (isNpmModule(componentPath)) {
// Build usingComponents relative path for modules in npm folder
const npmFolderPath = distComponentConfigPath.slice(0, distComponentConfigPath.indexOf('npm') + 'npm'.length);

const predictComponentPathInNpmFolder = join(npmFolderPath, '/', componentPath);
const relativeComponentPath = normalizeNpmFileName(addRelativePathPrefix(relative(dirname(distComponentConfigPath), predictComponentPathInNpmFolder)));

// component from node module
const realComponentPath = resolveModule.sync(componentPath, { basedir: this.resourcePath, paths: [this.resourcePath], preserveSymlinks: false });
const relativeComponentPath = normalizeNpmFileName(addRelativePathPrefix(relative(dirname(sourceNativeMiniappScriptFile), realComponentPath)));

componentConfig.usingComponents[key] = normalizeOutputFilePath(removeExt(relativeComponentPath));
// Native miniapp component js file will loaded by script-loader
dependencies.push({
Expand All @@ -144,6 +172,7 @@ module.exports = function scriptLoader(content) {
}
}
}

if (!existsSync(distComponentConfigPath)) {
ensureFileSync(distComponentConfigPath);
writeJSONSync(distComponentConfigPath, componentConfig);
Expand Down Expand Up @@ -207,25 +236,29 @@ module.exports = function scriptLoader(content) {
}
const miniappComponentDir = miniappComponentPath.slice(0, miniappComponentPath.lastIndexOf('/'));
const source = join(sourcePackagePath, miniappComponentDir);
const target = normalizeNpmFileName(join(outputPath, 'npm', relative(rootNodeModulePath, sourcePackagePath), miniappComponentDir));
const target = normalizeNpmFileName(join(outputPath, 'npm', isHardLink ? npmName : relative(rootNodeModulePath, sourcePackagePath), miniappComponentDir));
outputDir(source, target, {
isThirdMiniappComponent,
resourcePath: this.resourcePath
});

// Modify referenced component location according to the platform
const originalComponentConfigPath = join(sourcePackagePath, miniappComponentPath + '.json');
const distComponentConfigPath = normalizeNpmFileName(join(outputPath, 'npm', relative(rootNodeModulePath, sourcePackagePath), miniappComponentPath + '.json'));
const distComponentConfigPath = normalizeNpmFileName(join(outputPath, 'npm', isHardLink ? npmName : relative(rootNodeModulePath, sourcePackagePath), miniappComponentPath + '.json'));

checkUsingComponents(dependencies, originalComponentConfigPath, distComponentConfigPath, sourceNativeMiniappScriptFile, npmName);
}
if (isThirdMiniappComponent) {
const source = dirname(this.resourcePath);
const target = dirname(normalizeNpmFileName(join(outputPath, 'npm', relative(rootNodeModulePath, this.resourcePath))));

// For hard link case, there won't be pkgname present in resource path, so we need to add it manually
const target = dirname(normalizeNpmFileName(join(outputPath, 'npm', isHardLink ? join(npmName, relative(currentPackagePath, this.resourcePath)) : relative(rootNodeModulePath, this.resourcePath))));
outputDir(source, target);
outputFile(rawContent);

const originalComponentConfigPath = removeExt(this.resourcePath) + '.json';
const distComponentConfigPath = normalizeNpmFileName(join(outputPath, 'npm', relative(rootNodeModulePath, removeExt(this.resourcePath) + '.json')));
const distComponentConfigPath = normalizeNpmFileName(join(outputPath, 'npm', isHardLink ? join(npmName, relative(currentPackagePath, removeExt(this.resourcePath) + '.json')) : relative(rootNodeModulePath, removeExt(this.resourcePath) + '.json') ));

checkUsingComponents(dependencies, originalComponentConfigPath, distComponentConfigPath, this.resourcePath, npmName);
}

Expand Down
33 changes: 33 additions & 0 deletions packages/jsx2mp-loader/src/utils/getRootNodeModulePath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { join, relative, sep } = require('path');

function getRootNodeModulePath(root, current) {
const relativePathArray = relative(root, current).split(sep) || [];

if (relativePathArray.find((item) => item === '..')) {
/**
* Package hoist case exist while `..` is presented in relative path array
*/
const resourcePathArray = current.split('node_modules') || [];

if (resourcePathArray.length === 1) {
/**
* current file is not in node_modules folder means hard link case, so we need to use the root node_modules folder
*/
return join(root, 'node_modules');
} else {
/**
* If relative path array length is greater than 1, it means that the current file is in a nested node_modules folder, so we need to dig into the deepest node_modules folder
*/
return join(
resourcePathArray
.slice(0, resourcePathArray.length - 1)
.join('node_modules'),
'node_modules'
);
}
} else {
return join(root, 'node_modules');
}
}

module.exports = getRootNodeModulePath;