gulp-plugin / alias Goto Github PK
View Code? Open in Web Editor NEWResolve TypeScript import aliases and paths.
License: MIT License
Resolve TypeScript import aliases and paths.
License: MIT License
this test case will fail:
['should support wild card aliases']: {
options: { config: { paths: { '@/*': ['./src/*'] } } },
path: './src/pages/Page.ts',
input: "import module from 'module'\nimport Component from '@/pages/components'",
output: "import module from 'module'\nimport Component from './components'",
},
thank you very much for this, i was working on something similar recently myself.
your published version on npm has exports = aliasPlugin instead of module.exports = aliasPlugin
(so it won't work from a regular gulpfile without babel or ts register)
here is a working copy
const path = require("path");
const PluginError = require("plugin-error");
const map = require("map-stream");
function parseImports(file, dir) {
const results = file.map((line, index) => {
const imports = findImport(line);
if (imports === null) {
return null;
}
return {
path: dir,
index,
import: imports,
};
});
return results.filter((value) => {
return value !== null && value !== undefined;
});
}
function findImport(line) {
const matches = line.match(/from (["'])(.*?)\1/);
if (!matches) {
return null;
}
if (matches.length > 3) {
throw new PluginError('gulp-ts-alias', 'Multiple imports on the same line are currently not supported!');
}
return matches[2];
}
function resolveImports(file, imports, options) {
const { baseUrl, paths } = options;
const aliases = {};
for (const alias in paths) {
if (!paths.hasOwnProperty(alias)) {
continue;
}
let resolved = alias;
if (alias.endsWith('/*')) {
resolved = alias.replace('/*', '/');
}
aliases[resolved] = paths[alias];
}
const lines = [...file];
for (const imported of imports) {
const line = file[imported.index];
let resolved = '';
for (const alias in aliases) {
if (!aliases.hasOwnProperty(alias)) {
continue;
}
if (!imported.import.startsWith(alias)) {
continue;
}
const choices = aliases[alias];
if (choices !== undefined && choices !== null) {
resolved = choices[0];
if (resolved.endsWith('/*')) {
resolved = resolved.replace('/*', '/');
}
resolved = imported.import.replace(alias, resolved);
}
else {
continue;
}
break;
}
if (resolved.length < 1) {
continue;
}
let relative = path.relative(path.dirname(imported.path), baseUrl || './');
relative = path.join(relative, resolved);
relative = path.relative(path.dirname(imported.path), path.resolve(path.dirname(imported.path), relative));
relative = relative.replace(/\\/g, '/');
if (relative.length === 0 || !relative.startsWith('.')) {
relative = './' + relative;
}
lines[imported.index] = line.replace(imported.import, relative);
}
return lines;
}
const aliasPlugin = (pluginOptions) => {
if (pluginOptions.configuration === undefined || pluginOptions.configuration === null) {
throw new PluginError('gulp-ts-alias', 'The \"configuration\" option cannot be empty. Provide the tsconfig or compilerOptions object.');
}
const compilerOptions = pluginOptions.configuration.compilerOptions || pluginOptions.configuration;
if (compilerOptions.paths === undefined || compilerOptions.paths === null) {
throw new PluginError('gulp-ts-alias', 'Unable to find the \"paths\" property in the supplied configuration!');
}
if (compilerOptions.baseUrl === undefined || compilerOptions.baseUrl === '.') {
compilerOptions.baseUrl = './';
}
return map((file, cb) => {
if (file.isNull() || !file.contents) {
return cb(null, file);
}
if (file.isStream()) {
return cb(new PluginError('gulp-ts-alias', 'Streaming is not supported.'));
}
const contents = file.contents;
if (contents === null) {
return cb(null, file);
}
const lines = contents.toString().split('\n');
const imports = parseImports(lines, file.path);
if (imports.length === 0) {
return cb(null, file);
}
const resolved = resolveImports(lines, imports, compilerOptions);
file.contents = Buffer.from(resolved.join('\n'));
cb(null, file);
});
};
module.exports = aliasPlugin;
for example i have ts config:
{"compilerOptions": {
"baseUrl": "./src"
}}
and have two source files
// pages/MainPage.ts
import {Grid} from "core/Grid";
// core/Grid.ts
export class Grid {}
i need to resolve import path in pages/MainPage.ts
from "core/Grid" to "../core/Grid"
because i have baseUrl in ts config and typescript understand types by path "core/Grid"
I need to write actual tests for this, seeing as people seem to actually have interest in using this package.
function resolveImports(
file: ReadonlyArray<string>,
imports: FileData[],
options: ts.CompilerOptions
): string[] {
const { baseUrl, paths, cwd } = options
...
}
cwd is not in type 'ts.CompilerOptions',it cause "error TS5023: Unknown compiler option 'cwd'."
Summary
Gulp build is failing with Unable to find the "paths" property in the supplied configuration!
error, if configuration doesn't set compilerOptions.paths
, but instead extends another configuration using extends
property.
Example
├── lib
│ └── // ts-files
├── src
│ └── // ts-files
├── base.tsconfig.json
├── tsconfig.json
└── gulpfile.js
// tsconfig.json
{
"extends": "./base.tsconfig.json"
}
// base.tsconfig.json
{
"compilerOptions": {
"paths": {
"~/*": ["./lib/*"]
}
}
}
// gulpfile.js
const { src, dest } = require("gulp");
const typescript = require('gulp-typescript');
const alias = require('gulp-ts-alias');
const project = typescript.createProject('tsconfig.json');
function build() {
const compiled = src('./src/**/*.ts')
.pipe(alias({ configuration: project.config }))
.pipe(project());
return compiled.js
.pipe(dest('build/'))
}
what about change matches
in findImport function to:
const matches = line.match(/from (["'])(.*?)\1/) || line.match(/import\((["'])(.*?)\1\)/);
so, we can do some thing like this:
import("src/test").then(test => test());
this will fix double line imports, just make it recursively call for each item in the result array. Needed this for react-hot-loader with sucrase. much fastness.
function findImport(line) {
const matches = line.match(/from (["'])(.*?)\1/) || line.match(/import\((["'])(.*?)\1\)/) || line.match(/require\((["'])(.*?)\1\)/);
if (!matches) {
return null;
}
const multiple = [/from (["'])(.*?)\1/g, /import\((["'])(.*?)\1\)/g, /require\((["'])(.*?)\1\)/g].some((exp) => {
const results = line.match(exp);
console.log(results)
//
//return results && results.length > 1
if(results && results.length > 1){
results.map(r => findImport(r))
}
})
// if (multiple) {
// throw new Error('Multiple imports on the same line are currently not supported!');
// }
return matches[2];
}
this config helper looks in src and creates an alias for all the top level folders.. this should handle almost all cases for people with 0 config instead of throwing an error
const fs = require('fs')
const sourceDirs = fs.readdirSync('src').filter(f => (f.indexOf(".") < 1))
let createConfig = () => {
//todo , merge this with fs read tsconfig + user options + maybe like a web_modules
const config = {
baseUrl: ".",
paths:{}
}
for (var x of sourceDirs) {
config.paths[`${x}/*`] = [`src/${x}/*`]
}
//return JSON.stringify(config)
return config
}
3
here is my version (for browserify) it works great
var path = require('path')
const {Transform , PassThrough} = require('stream')
/** type{(file: ReadonlyArray<string>, dir: string)}*/
function parseImports(file, dir) {
const results = file.map((line, index) => {
const imports = findImport(line);
if (imports === null) {
return null;
}
return {
path: dir,
index,
import: imports,
};
});
return results.filter((value) => {
return value !== null && value !== undefined;
});
}
/**
* @type{(line: string)}
* @returns {string | null}
*/
function findImport(line) {
const matches = line.match(/from (["'])(.*?)\1/) || line.match(/import\((["'])(.*?)\1\)/) || line.match(/require\((["'])(.*?)\1\)/);
if (!matches) {
return null;
}
const multiple = [/from (["'])(.*?)\1/g, /import\((["'])(.*?)\1\)/g, /require\((["'])(.*?)\1\)/g].some((exp) => {
const results = line.match(exp);
console.log(results)
//
//return results && results.length > 1
if(results && results.length > 1){
results.map(r => findImport(r))
}
})
// if (multiple) {
// throw new Error('Multiple imports on the same line are currently not supported!');
// }
return matches[2];
}
/** @type {(file: ReadonlyArray<string>, imports: FileData[], options: CompilerOptions) => string[]) }
* @returns : string[]
*/
function resolveImports(file, imports, options) {
const { baseUrl, paths } = options;
/** @type { [key: string]: string[] | undefined } */
const aliases = {};
for (const alias in paths) {
/* istanbul ignore else */
if (paths.hasOwnProperty(alias)) {
let resolved = alias;
if (alias.endsWith('/*')) {
resolved = alias.replace('/*', '/');
}
aliases[resolved] = paths[alias];
}
}
const lines = [...file];
for (const imported of imports) {
const line = file[imported.index];
let resolved = '';
for (const alias in aliases) {
/* istanbul ignore else */
if (aliases.hasOwnProperty(alias) && imported.import.startsWith(alias)) {
const choices= aliases[alias];
if (choices != undefined) {
resolved = choices[0];
if (resolved.endsWith('/*')) {
resolved = resolved.replace('/*', '/');
}
resolved = imported.import.replace(alias, resolved);
break;
}
}
}
if (resolved.length < 1) {
continue;
}
let relative = path.relative(path.dirname(imported.path), baseUrl || './');
relative = path.join(relative, resolved);
relative = path.relative(path.dirname(imported.path), path.resolve(path.dirname(imported.path), relative));
relative = relative.replace(/\\/g, '/');
if (relative.length === 0 || !relative.startsWith('.')) {
relative = './' + relative;
}
lines[imported.index] = line.replace(imported.import, relative);
}
return lines;
}
const fs = require('fs')
const sourceDirs = fs.readdirSync('src').filter(f => (f.indexOf(".") < 1))
let createConfig = () => {
//todo , merge this with fs read tsconfig + user options + maybe like a web_modules
const config = {
baseUrl: ".",
paths:{}
}
for (var x of sourceDirs) {
config.paths[`${x}/*`] = [`src/${x}/*`]
}
//return JSON.stringify(config)
return config
}
const compilerOptions = createConfig()
function compile(filePath, chunk) {
//const lines = chunk.toString('utf8').split('\n');
const lines = chunk.split('\n');
const imports = parseImports(lines, filePath)
const code = resolveImports(lines, imports, compilerOptions).join('\n')
return code
}
const unpathifyStream = (file) => {
if (!/\.tsx?$|\.jsx?$/.test(file) || file.indexOf("node_modules")>0 || file.indexOf("src")<0) {
return new PassThrough();
}
var _transform = new Transform()
_transform._write = (chunk, encoding, next) => {
_transform.push(compile(file, chunk.toString('utf8')))
next();
}
return _transform
}
module.exports = unpathifyStream
At the moment this plugin only supports new import style module imports. This could probably be relatively easily adapted to support require()
imports as well.
I personally never use them much, unless I'm requiring a third party module. This makes the need for path resolution in require less useful, because I'd never make any of my own modules need to be used with require()
.
If anyone finds they need path resolution for this, let me know so I can work on it.
I think this issue is best explained by an example:
Project structure:
dist/
'-- testing/
src/
'-- index.ts
tasks/
'-- build.js
tsconfig.json
tsconfig.json
:
{
// * snip *
"compilerOptions": {
"baseUrl": ".",
"paths": {
"broken-module": [ "node_modules/@myfork/broken-module" ]
}
// * snip *
}
}
index.ts
:
import xxx from 'broken-module'
// * snip *
build.js
:
const { src, dest } = require('gulp');
const ts = require('gulp-typescript');
const alias = require('gulp-ts-alias');
const tsProject = ts.createProject('../tsconfig.json');
exports.default = function compile() {
const tsResult =
src('../src/**/*.ts')
.pipe(alias({ configuration: tsProject.config }))
.pipe(dest('../dist/testing')) // Intermediate output, used to debug generated imports
.pipe(tsProject());
return tsResult.js.pipe(dest('../dist'));
}
Resulting intermediate dist/testing/index.ts
:
import xxx from '../tasks/node_modules/@myfork/broken-module'
// Expected: '@myfork/broken-module' or '../../node_modules/@myfork/broken-module'
My guess would be that it resolves TS's baseUrl
against cwd
during build time instead of the location of tsconfig.json
. Setting the baseUrl
to ../
solves the issue (but breaks tsc
), further strengthening my suspicions.
Example
// let ex;
// try {
// ex = require("example");
// } catch (err) {
// ex = require("example1");
// }
This is fine to cause Multiple imports on the same line are currently not supported!
But it should not try to resolve path if code was commented.
// gulpfile.js
gulp
.src(entry)
.pipe(alias({ configuration: tsProject.config }))
.pipe(tsProject())
.pipe(gulp.dest('./dist'));
// src/server/controllers/index.ts
import IndexController from '@/controllers/IndexController'; // cannot find module “@/controllers/IndexController” or other type declarations
// src/server/controllers/IndexController.ts
import Koa from 'koa';
export default class IndexController {
constructor() {}
async actionIndex(ctx: Koa.Context, next: Koa.Next) {
const books = new Books();
const res = await books.getData();
ctx.body = res;
}
}
// tsconfig.json
{
"compilerOptions": {
"target": "es5" ,
"module": "commonjs",
"paths": {
"@/controllers": ["./src/server/controllers"],
"@/models": ["./src/server/models"]
} ,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Although the app run normally, I want to resolve the errors in index.ts
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.