Skip to content

Commit

Permalink
Merge pull request #1 from slune-org/hash
Browse files Browse the repository at this point in the history
Add ability to give hash (and more) to asset names
  • Loading branch information
sveyret authored Jan 14, 2020
2 parents e62f6c9 + 95cb19e commit 735bcdb
Show file tree
Hide file tree
Showing 14 changed files with 265 additions and 75 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ test/*.js
.nyc_output/
coverage/

# Yarn
# Npm and Yarn
package-lock.json
yarn-error.log
yarn.lock

Expand Down
10 changes: 10 additions & 0 deletions .nycrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
extends: '@istanbuljs/nyc-config-typescript'
include:
- 'src/**'
exclude:
- '**/*.spec.ts'
reporter:
- lcov
- text
all: true
31 changes: 27 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const foobar = 'assets/foobar.ico'

# Language/langue

Because French is my native language, finding all documents and messages in French is not an option. Other translations are welcome.
Because French is my native language, finding all documents and messages in French is mandatory. Other translations are welcome.

Anyway, because English is the language of programming, the code, including variable names and comments, are in English.

Expand Down Expand Up @@ -62,7 +62,7 @@ Using this transformer to transpile the web pages (not for `Webpack`!), you will
The transformer accepts the following parameters:

- `assetsMatch`: a regular expression used to select asset imports, e.g., for all `.png` files, `assetsMatch = "\\.png$"`. This parameter is mandatory.
- `targetPath`: a path which is prefixed to the file name, i.e. the same as the `publicPath` you may have defined in the `output` parameter of `Webpack`. This parameter is optional.
- `targetName`: a template similar to [Webpack file-loader name](https://webpack.js.org/loaders/file-loader/#name) used to convert the name of the asset. If you defined a `publicPath` in the `output` parameter of `Webpack`, then you will probably need to specify this path here too. This parameter is optional and defaults to `[hash].[ext]`.

There is currently no way of declaring a transformer in the vanilla `typescript` compiler. If you do not want to write your own compiler using the `typescript` API, you can use the `ttypescript` wrapper. Below is explained how.

Expand Down Expand Up @@ -91,7 +91,7 @@ Then, configure your `tsconfig.json`
{
"transform": "ts-transform-asset",
"assetsMatch": "\\.png$",
"targetPath": "assets"
"targetName": "assets/[name]-[hash].[ext]"
}
]
}
Expand Down Expand Up @@ -163,4 +163,27 @@ const url: string = image

# 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`.
## Prior to version 3.x.x

Prior to version 3.x.x, there was a configuration entry `targetPath` which was the prefix used to add to the target asset name. Everything is now defined in the new `targetName` entry. Converting from previous to current configuration is as simple as the below example:

```diff
"transform": "ts-transform-asset",
"assetsMatch": "\\.png$",
- "targetPath": "assets"
+ "targetName": "assets/[name].[ext]"
}
]
```

## Prior to version 2.x.x

In addition to the previous modifications, note that prior to version 2.x.x, transformer was of type `config`. Since version 2.0.0, transformer is of type `program`, 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`:

```diff
{
"transform": "ts-transform-asset",
- "type": "config",
"assetsMatch": "\\.png$",
"targetName": "assets/[name].[ext]"
```
31 changes: 27 additions & 4 deletions doc/fr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const foobar = 'assets/foobar.ico'

# Langue

Le français étant ma langue maternelle, fournir les documents et messages en français n'est pas une option. Les autres traductions sont bienvenues.
Le français étant ma langue maternelle, fournir les documents et messages en français est obligatoire. Les autres traductions sont bienvenues.

Cependant, l'anglais étant la langue de la programmation, le code, y compris les noms de variable et commentaires, sont en anglais.

Expand Down Expand Up @@ -54,7 +54,7 @@ L'utilisation de ce transformateur pour transpiler les pages web (pas pour `Webp
Le transformateur accepte les paramètres suivants :

- `assetsMatch`: une expression rationnelle utilisée pour sélectionner les imports de fichiers annexes, par exemple, pour tous les fichiers `.png`, `assetsMatch = "\\.png$"` — ce paramètre est obligatoire;
- `targetPath`: un chemin qui est préfixé au nom de fichier, c'est le `publicPath` que vous pouvez avoir défini dans le paramètre `output` de `Webpack` — ce paramètre est optionnel.
- `targetName`: un patron similaire au [Webpack file-loader name](https://webpack.js.org/loaders/file-loader/#name) utilisé pour convertir le nom du fichier annexe — si vous définissez un `publicPath` dans le paramètre `output` de `Webpack`, vous aurez probablement besoin de spécifier ce chemin ici également — ce paramètre est optionnel et vaut `[hash].[ext]` par défaut.

Il n'y a actuellement pas moyen de déclarer un transformateur dans le compilateur `typescript` standard. Si vous ne souhaitez pas écrire votre propre compilateur en utilisant l'API `typescript`, vous pouvez utiliser la surcouche `ttypescript`. Les explications sont données ci-dessous.

Expand Down Expand Up @@ -83,7 +83,7 @@ Ensuite, configurez votre `tsconfig.json`
{
"transform": "ts-transform-asset",
"assetsMatch": "\\.png$",
"targetPath": "assets"
"targetName": "assets/[name]-[hash].[ext]"
}
]
}
Expand Down Expand Up @@ -155,4 +155,27 @@ const url: string = image

# 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`.
## Versions antérieures à 3.x.x

Avant la version 3.x.x, il y avait une entrée de configuration nommée `targetPath` qui définissait un chemin préfixé au nom de fichier. Tout est maintenant défini par la nouvelle entrée `targetName`. La conversion d'une ancienne configuration vers la nouvelle est aussi simple que l'exemple ci-dessous :

```diff
"transform": "ts-transform-asset",
"assetsMatch": "\\.png$",
- "targetPath": "assets"
+ "targetName": "assets/[name].[ext]"
}
]
```

## Versions antérieur à 2.x.x

En plus des modifications précédentes, notez qu'avant la version 2.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` :

```diff
{
"transform": "ts-transform-asset",
- "type": "config",
"assetsMatch": "\\.png$",
"targetName": "assets/[name].[ext]"
```
40 changes: 14 additions & 26 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "ts-transform-asset",
"version": "2.0.0",
"version": "3.0.0",
"description": "Typescript transformer used to convert asset imports to file names",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/sveyret/ts-transform-asset.git"
"url": "git+https://github.com/slune-org/ts-transform-asset.git"
},
"scripts": {
"prepublishOnly": "npm run all",
Expand All @@ -20,29 +20,15 @@
"debug": "npm run debug:watch",
"debug:watch": "npm-watch test"
},
"nyc": {
"extension": [
".ts"
],
"include": [
"src/**"
],
"exclude": [
"**/*.spec.ts"
],
"reporter": [
"lcov",
"text"
],
"all": true
},
"keywords": [
"typescript",
"transform",
"asset",
"filename"
"filename",
"webpack",
"file-loader"
],
"author": "Stéphane Veyret",
"author": "Slune",
"license": "MIT",
"watch": {
"test": {
Expand All @@ -57,15 +43,17 @@
"typescript": "^3.0.0"
},
"devDependencies": {
"@types/chai": "4.2.6",
"@istanbuljs/nyc-config-typescript": "1.0.1",
"@types/chai": "4.2.7",
"@types/mocha": "5.2.7",
"@types/node": "12.12.16",
"@types/node": "13.1.6",
"chai": "4.2.0",
"mocha": "6.2.2",
"nyc": "14.1.1",
"mocha": "7.0.0",
"nyc": "15.0.0",
"rimraf": "3.0.0",
"ts-node": "8.5.4",
"source-map-support": "0.5.16",
"ts-node": "8.6.2",
"tslint": "5.20.1",
"typescript": "3.7.3"
"typescript": "3.7.4"
}
}
72 changes: 65 additions & 7 deletions src/AssetModuleManager.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import { basename, join } from 'path'
import { HexBase64Latin1Encoding, createHash } from 'crypto'
import { existsSync, readFileSync } from 'fs'
import { basename, dirname, join, parse, relative, sep } 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 {
/**
* The directory of the current file, root of module search.
*/
private currentPath: string

/**
* Create the object.
*
* @param assetsMatch - The regular expression for detecting matching modules.
* @param targetPath - The public target path for the assets.
* @param targetName - The public target name to use for the assets.
* @param filename - The name of the file currently being transformed.
* @param basePath - The base path of the project.
*/
public constructor(private assetsMatch: RegExp, private targetPath?: string) {}
public constructor(
private assetsMatch: RegExp,
private targetName: string,
filename: string,
private basePath: string
) {
this.currentPath = dirname(filename)
}

/**
* Build the module name as it should be used inside source file, if the module specifier matches the
Expand All @@ -22,19 +38,61 @@ export default class AssetModuleManager {
* @returns The build module name or undefined if not matching.
*/
public buildName(moduleSpecifier?: Expression): string | undefined {
/* istanbul ignore else */
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 this.interpolateName(moduleName)
}
}
return undefined
}

/**
* Create the asset name using `targetName` template and given module name.
* @param moduleName The name of module to use as interpolation source.
*/
private interpolateName(moduleName: string) {
const modulePath = join(this.currentPath, moduleName)
const parsed = parse(modulePath)
/* istanbul ignore next */
const ext = parsed.ext ? parsed.ext.substr(1) : 'bin'
/* istanbul ignore next */
const filename = parsed.name || 'file'

let directory =
relative(this.basePath, parsed.dir)
.replace(/\\/g, '/')
.replace(/\.\.(\/)?/g, '_$1') + sep
let folder = ''
if (directory.length === 1) {
directory = ''
} else {
folder = basename(directory)
}

let url = this.targetName
if (existsSync(modulePath)) {
const content = readFileSync(modulePath)
url = url.replace(
/\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*))?(?::(\d+))?\]/gi,
(_, hashType: string, digestType: HexBase64Latin1Encoding | '', maxLength: string) => {
const hash = createHash(hashType || 'md5')
hash.update(content)
return hash.digest(digestType || 'hex').substr(0, parseInt(maxLength, 10) || 9999)
}
)
}

url = url
.replace(/\[ext\]/gi, ext)
.replace(/\[name\]/gi, filename)
.replace(/\[path\]/gi, directory)
.replace(/\[folder\]/gi, folder)
return url
}
}
1 change: 1 addition & 0 deletions src/DeclarationNodeFinder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default class DeclarationNodeFinder {
const symbol = this.typeChecker.getSymbolAtLocation(node)
if (symbol) {
const declarations = symbol.getDeclarations()
/* istanbul ignore else */
if (declarations && declarations.length === 1) {
return declarations[0]
}
Expand Down
2 changes: 1 addition & 1 deletion src/PluginConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default interface PluginConfig {
assetsMatch: string
targetPath?: string
targetName?: string
}
6 changes: 4 additions & 2 deletions src/compile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,21 @@ const TS_CONFIG: CompilerOptions = {

export default function compile(
testName: string,
rootDir: string | undefined,
input: string[],
assetsMatch: string,
targetPath?: string
targetName?: string
): void {
const options: CompilerOptions = {
...TS_CONFIG,
rootDir,
outDir: `dist/test/${testName}`,
}
const compilerHost = createCompilerHost(options)
const program = createProgram(input, options, compilerHost)

const emitResult = program.emit(undefined, undefined, undefined, undefined, {
before: [transform(program, { assetsMatch, targetPath })],
before: [transform(program, { assetsMatch, targetName })],
})

const allDiagnostics = getPreEmitDiagnostics(program).concat(emitResult.diagnostics)
Expand Down
Loading

0 comments on commit 735bcdb

Please sign in to comment.