Skip to content

Commit

Permalink
Code rewrite, version 2
Browse files Browse the repository at this point in the history
  • Loading branch information
sveyret committed Dec 12, 2019
1 parent ed9558c commit e62f6c9
Show file tree
Hide file tree
Showing 19 changed files with 545 additions and 131 deletions.
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ tslint.yaml

# Tests
dist/**/*.spec.*
dist/test/

# Coverage
.nyc_output/
Expand Down
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"arrowParens": "avoid",
"printWidth": 108,
"semi": false,
"singleQuote": true,
"trailingComma": "es5"
}
87 changes: 81 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,23 @@

# ts-transform-asset - Typescript transformer for asset imports

This transformer will simply convert an import like:
This transformer will simply convert imports or reexports like:

```typescript
import * as foo from "../images/bar.png";
import foo from './images/foo.gif'
import * as bar from '../images/bar.png'
export { default } from './foobar.svg'
export { default as foobar } from './foobar.ico'
```

to:

```typescript
const foo = "assets/bar.png";
const foo = 'assets/foo.gif'
const bar = 'assets/bar.png'
const foobar_svg_1 = 'assets/foobar.svg'
export default foobar_svg_1
export const foobar = 'assets/foobar.ico'
```

# Language/langue
Expand All @@ -42,9 +49,11 @@ $ yarn add --dev ts-transform-asset

# Why would I need that?

Imagine you have a project creating some web pages. This project is packed with `Webpack`, creating a `bundle.js`. In the same time, the `Webpack` `file-loader` plugin is moving all assets into the target directory. This is done by setting some `import * as foo from '../images/bar.png` for your assets in source code.
You have a project using `Webpack` with the `file-loader` plugin. When this plugin finds an `import foo from "./images/foo.gif"`, it copies `foo.gif` to an assets directory and change the usage of `foo` using the public path to the file. Great.

Then, you have another project, which contains the web server. This server, which depends on the previous one, will take all assets and bundle to serve them to the clients. But you also want to do some server side rendering. And for this, you prefer using the transpiled `javascript` and definition files instead of the minified and untyped bundle. But this is not working, because your server do not know what to do with your assets files.
Then, you have another project, which contains the web server. This server, which depends on the previous project, will take the bundle and all assets to serve them to the clients. But you also want to do some server side rendering (SSR). And for this, you cannot use the bundle created by `Webpack` because it does not have an appropriate entry point for the server (e.g. you need to use a `StaticRouter` instead of a `BrowserRouter`). Or maybe you prefer using the transpiled `javascript` and definition files instead of the minified and untyped bundle.

Unfortunately, this is not working, because your server do not know what to do with your assets files.

Using this transformer to transpile the web pages (not for `Webpack`!), you will convert your imports into constants with the URL of where resources should be found, and dependents project will be able to work without any more configuration.

Expand Down Expand Up @@ -81,11 +90,77 @@ Then, configure your `tsconfig.json`
"plugins": [
{
"transform": "ts-transform-asset",
"type": "config",
"assetsMatch": "\\.png$",
"targetPath": "assets"
}
]
}
}
```

## Code

Your TypeScript code should already be well written if you are using `Webpack` and the `file-loader`. If not, you can follow the instructions below.

### Module declaration

Before using them, you need to declare the new module types, for example in an `assets.d.ts` file like this:

```typescript
declare module '*.png' {
const content: string
export default content
}
```

Older versions of `file-loader` (before 5.0.2) where not using a default export. You should then declare module types this way in this case:

```typescript
declare module '*.png' {
const content: string
export = content
}
```

### Asset import

When the (module) file is to be used:

```typescript
import image from './image.png'

const url: string = image
```

It is even possible to re-export the asset file:

```typescript
export { default as image } from './image.png'
```

Then, in another file:

```typescript
import { image } from '../images'

const url: string = image
```

For older versions of `file-loader` (before 5.0.2), only namespace import is possible:

```typescript
import * as image from './image.png'

const url: string = image
```

# Notices

- The transformer will not detect nor modify any `require` statement. It is advised to run it in the `before` phase of the compilation, before the code is converted to an older version of `ECMAScript`.
- The transformer either modify the code if it conforms to what is expected, or do not touch it at all. There is an exception anyway for the re-export declarations: if the source module matches the given parameters, but the exported property is not `default`, then this export property will be removed.
- Please file an issue if you have any problem using the transformer. Even if I cannot give a response time, I will do my best to correct problems or answer questions.
- Contributions are of course always welcome.

# Migration

Note that in versions 1.x.x, transformer was of `config` type. Since version 2.0.0, transformer is of `program` type, which is the default. If you are upgrading from an older version and using `ttypescript`, you have to update the `plugin` configuration in `tsconfig.json`.
87 changes: 81 additions & 6 deletions doc/fr/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
# ts-transform-asset - Transformateur typescript pour l'import des fichiers annexes

Ce transformateur se content de convertir les imports tel que :
Ce transformateur se contente de convertir les imports tel que :

```typescript
import * as foo from "../images/bar.png";
import foo from './images/foo.gif'
import * as bar from '../images/bar.png'
export { default } from './foobar.svg'
export { default as foobar } from './foobar.ico'
```

en :

```typescript
const foo = "assets/bar.png";
const foo = 'assets/foo.gif'
const bar = 'assets/bar.png'
const foobar_svg_1 = 'assets/foobar.svg'
export default foobar_svg_1
export const foobar = 'assets/foobar.ico'
```

# Langue
Expand All @@ -34,9 +41,11 @@ $ yarn add --dev ts-transform-asset

# Pourquoi aurai-je besoin de ça ?

Imaginez que vous avez un projet qui crée des pages web. Ce projet est empaqueté avec `Webpack`, qui génère un `bundle.js`. Durant ce processus, l'extension `file-loader` de `Webpack` déplace les fichiers annexes dans le répertoire de destination. Ceci est fait en indiquant des `import * as foo from '../images/bar.png` pour ces fichiers annexes dans le code source.
Vous avez un projet empaqueté avec `Webpack` et l'extension `file-loader`. Lorsque cette extension trouve un `import foo from "./images/foo.gif"`, elle copie `foo.gif` vers un répertoire de fichiers annexes et modifie l'utilisation de `foo` en utilisant le chemin public vers le fichier. Bien.

Imaginez maintenant que vous avez un autre projet qui contient le serveur web. Ce serveur, qui dépend du projet précédent, prendra tous ces fichiers annexes ainsi que le paquet `bundle.js` pour servir les clients. Mais vous voulez également faire du rendu côté serveur. Pour cela, vous préférez utiliser la version transpilée avec les fichiers `javascript` et de définition plutôt que le paquet minimisé et sans type. Sauf que cela ne fonctionne pas car le serveur ne sait pas quoi faire des imports définis dans les pages web.
Maintenant, vous avez un autre projet qui contient le serveur web. Ce serveur, qui dépend du projet précédent, prendra le paquet généré et tous ces fichiers annexes pour les servir aux clients. Mais vous voulez également faire du rendu côté serveur (SSR). Et pour cela, vous ne pouvez pas utiliser le paquet généré par `Webpack` parce qu'il n'a pas un point d'entré approprié pour le serveur (il utilise un `BrowserRouter` au lieu d'un `StaticRouter`, par exemple). Ou peut-être préférez-vous utiliser la version transpilée avec les fichiers `javascript` et les fichiers de définition plutôt que le paquet minimisé et sans type.

Malheureusement, cela ne fonctionne pas car le serveur ne sait pas quoi faire des imports de fichiers annexes.

L'utilisation de ce transformateur pour transpiler les pages web (pas pour `Webpack` !) convertira ces imports en constantes contenant l'URL où les ressources peuvent être trouvées, et les projets dépendants fonctionneront sans plus de configuration.

Expand Down Expand Up @@ -73,11 +82,77 @@ Ensuite, configurez votre `tsconfig.json`
"plugins": [
{
"transform": "ts-transform-asset",
"type": "config",
"assetsMatch": "\\.png$",
"targetPath": "assets"
}
]
}
}
```

## Code source

Votre code source TypeScript devrait déjà être correctement écrit si vous utilisez `Webpack` et le `file-loader`. Dans le cas contraire, vous pouvez suivre les instructions ci-dessous.

### Déclaration des modules

Avant de pouvoir les utiliser, vous devez déclarer les nouveaux types de modules, dans un fichier `assets.d.ts`, par exemple :

```typescript
declare module '*.png' {
const content: string
export default content
}
```

Les anciennes versions de `file-loader` (avant la 5.0.2) n'utilisaient pas d'export par défaut. Les types de modules doivent plutôt être déclarés ainsi dans ce cas :

```typescript
declare module '*.png' {
const content: string
export = content
}
```

### Import des fichiers annexes

Lorsque le fichier (module) doit être utilisé :

```typescript
import image from './image.png'

const url: string = image
```

Il est également possible de ré-exporter les fichiers annexes :

```typescript
export { default as image } from './image.png'
```

Puis, dans un autre fichier :

```typescript
import { image } from '../images'

const url: string = image
```

Pour les anciennes versions de `file-loader` (avant la 5.0.2), seul l'import d'espace de nom est possible :

```typescript
import * as image from './image.png'

const url: string = image
```

# Notes

- Le transformateur ne détectera ni ne modifiera aucune instruction `require`. Il est conseillé de l'exécuter dans la phase de compilation `before`, avant que le code soit convertit dans une version plus ancienne d'`ECMAScript`.
- Le transformateur modifie le code s'il est conforme à ce qui est attendu, ou ne le touche pas du tout. Il y a toutefois une exception pour les déclarations de ré-exports : si le module source correspond aux paramètres donnés mais que la propriété exportée n'est pas `default`, alors cet propriété sera supprimée.
- Merci d'ouvrir un incident si vous avez un problème à l'utilisation de ce transformateur. Même si je ne peux pas garantir de délai de réponse, je ferai de mon mieux pour corriger les problèmes et répondre aux questions.
- Les contributions sont bien sûr bienvenues.

# Migration

Notez que dans les versions 1.x.x, le transformateur était de type `config`. Depuis la version 2.0.0, le transformateur est de type `program`, qui est le type par défaut. Si vous mettez à jour depuis une version plus ancienne et que vous utilisez `ttypescript`, vous devrez mettre à jour la configuration du `plugin` dans `tsconfig.json`.
37 changes: 19 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-transform-asset",
"version": "1.0.1",
"version": "2.0.0",
"description": "Typescript transformer used to convert asset imports to file names",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand All @@ -9,15 +9,16 @@
"url": "git+https://github.com/sveyret/ts-transform-asset.git"
},
"scripts": {
"watch": "npm-watch test",
"prepublishOnly": "npm run chain",
"chain": "npm run clean && npm run test && npm run build",
"build": "npm run transpile",
"test": "npm run lint && npm run testUnit",
"lint": "tslint src/**/*.ts",
"testUnit": "nyc mocha --require ts-node/register --require source-map-support/register 'src/**/*.spec.ts'",
"prepublishOnly": "npm run all",
"all": "npm run clean && npm run test && npm run build",
"build": "npm run build:typescript",
"build:typescript": "tsc",
"test": "npm run test:lint && npm run test:unit",
"test:lint": "tslint 'src/**/*.ts'",
"test:unit": "nyc mocha --require ts-node/register --require source-map-support/register 'src/**/*.spec.ts'",
"clean": "rimraf dist",
"transpile": "tsc"
"debug": "npm run debug:watch",
"debug:watch": "npm-watch test"
},
"nyc": {
"extension": [
Expand Down Expand Up @@ -56,15 +57,15 @@
"typescript": "^3.0.0"
},
"devDependencies": {
"@types/chai": "4.1.7",
"@types/mocha": "5.2.5",
"@types/node": "10.12.9",
"@types/chai": "4.2.6",
"@types/mocha": "5.2.7",
"@types/node": "12.12.16",
"chai": "4.2.0",
"mocha": "5.2.0",
"nyc": "13.1.0",
"rimraf": "2.6.2",
"ts-node": "7.0.1",
"tslint": "5.11.0",
"typescript": "3.1.6"
"mocha": "6.2.2",
"nyc": "14.1.1",
"rimraf": "3.0.0",
"ts-node": "8.5.4",
"tslint": "5.20.1",
"typescript": "3.7.3"
}
}
40 changes: 40 additions & 0 deletions src/AssetModuleManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { basename, join } from 'path'
import { Expression } from 'typescript'

/**
* Asset module manager can be used to detect asset modules and build a module name from a module
* specifier.
*/
export default class AssetModuleManager {
/**
* Create the object.
*
* @param assetsMatch - The regular expression for detecting matching modules.
* @param targetPath - The public target path for the assets.
*/
public constructor(private assetsMatch: RegExp, private targetPath?: string) {}

/**
* Build the module name as it should be used inside source file, if the module specifier matches the
* given `assetsMatch`.
*
* @param moduleSpecifier - The module specifier.
* @returns The build module name or undefined if not matching.
*/
public buildName(moduleSpecifier?: Expression): string | undefined {
if (moduleSpecifier) {
// Remove quotes for name
let moduleName: string = moduleSpecifier.getText()
moduleName = moduleName.substring(1, moduleName.length - 1)

// Check if matching assets pattern
if (this.assetsMatch.test(moduleName)) {
// Convert file name
moduleName = basename(moduleName)
this.targetPath && (moduleName = join(this.targetPath, moduleName))
return moduleName
}
}
return undefined
}
}
30 changes: 30 additions & 0 deletions src/DeclarationNodeFinder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Identifier, Node, TypeChecker } from 'typescript'

/**
* An object searching for a declaration node for a given identifier.
*/
export default class DeclarationNodeFinder {
/**
* Create the object.
*
* @param typeChecker - The type checker.
*/
public constructor(public typeChecker: TypeChecker) {}

/**
* Search for the declaration node of a given identifier.
*
* @param node - The node to search declaration for.
* @returns The declaration node if found, undefined otherwise.
*/
public find(node: Identifier): Node | undefined {
const symbol = this.typeChecker.getSymbolAtLocation(node)
if (symbol) {
const declarations = symbol.getDeclarations()
if (declarations && declarations.length === 1) {
return declarations[0]
}
}
return undefined
}
}
Loading

0 comments on commit e62f6c9

Please sign in to comment.