diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 1a630e9..0000000 --- a/.github/funding.yml +++ /dev/null @@ -1,3 +0,0 @@ -github: sindresorhus -open_collective: sindresorhus -custom: https://sindresorhus.com/donate diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c1870cf..d36e1a8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,10 +12,9 @@ jobs: node-version: - 14 - 12 - - 10 steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/index.d.ts b/index.d.ts index a857bdf..309e71a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,122 +1,167 @@ -declare namespace slugify { - interface Options { - /** - @default '-' +export interface Options { + /** + @default '-' - @example - ``` - import slugify = require('@sindresorhus/slugify'); + @example + ``` + import slugify from '@sindresorhus/slugify'; - slugify('BAR and baz'); - //=> 'bar-and-baz' + slugify('BAR and baz'); + //=> 'bar-and-baz' - slugify('BAR and baz', {separator: '_'}); - //=> 'bar_and_baz' + slugify('BAR and baz', {separator: '_'}); + //=> 'bar_and_baz' - slugify('BAR and baz', {separator: ''}); - //=> 'barandbaz' - ``` - */ - readonly separator?: string; + slugify('BAR and baz', {separator: ''}); + //=> 'barandbaz' + ``` + */ + readonly separator?: string; - /** - Make the slug lowercase. + /** + Make the slug lowercase. - @default true + @default true - @example - ``` - import slugify = require('@sindresorhus/slugify'); + @example + ``` + import slugify from '@sindresorhus/slugify'; - slugify('Déjà Vu!'); - //=> 'deja-vu' + slugify('Déjà Vu!'); + //=> 'deja-vu' - slugify('Déjà Vu!', {lowercase: false}); - //=> 'Deja-Vu' - ``` - */ - readonly lowercase?: boolean; + slugify('Déjà Vu!', {lowercase: false}); + //=> 'Deja-Vu' + ``` + */ + readonly lowercase?: boolean; - /** - Convert camelcase to separate words. Internally it does `fooBar` → `foo bar`. + /** + Convert camelcase to separate words. Internally it does `fooBar` → `foo bar`. - @default true + @default true - @example - ``` - import slugify = require('@sindresorhus/slugify'); + @example + ``` + import slugify from '@sindresorhus/slugify'; - slugify('fooBar'); - //=> 'foo-bar' + slugify('fooBar'); + //=> 'foo-bar' - slugify('fooBar', {decamelize: false}); - //=> 'foobar' - ``` - */ - readonly decamelize?: boolean; + slugify('fooBar', {decamelize: false}); + //=> 'foobar' + ``` + */ + readonly decamelize?: boolean; - /** - Add your own custom replacements. + /** + Add your own custom replacements. - The replacements are run on the original string before any other transformations. + The replacements are run on the original string before any other transformations. - This only overrides a default replacement if you set an item with the same key, like `&`. + This only overrides a default replacement if you set an item with the same key, like `&`. - Add a leading and trailing space to the replacement to have it separated by dashes. + Add a leading and trailing space to the replacement to have it separated by dashes. - @default [ ['&', ' and '], ['🦄', ' unicorn '], ['♥', ' love '] ] + @default [ ['&', ' and '], ['🦄', ' unicorn '], ['♥', ' love '] ] - @example - ``` - import slugify = require('@sindresorhus/slugify'); + @example + ``` + import slugify from '@sindresorhus/slugify'; + + slugify('Foo@unicorn', { + customReplacements: [ + ['@', 'at'] + ] + }); + //=> 'fooatunicorn' + + slugify('foo@unicorn', { + customReplacements: [ + ['@', ' at '] + ] + }); + //=> 'foo-at-unicorn' + + slugify('I love 🐶', { + customReplacements: [ + ['🐶', 'dogs'] + ] + }); + //=> 'i-love-dogs' + ``` + */ + readonly customReplacements?: ReadonlyArray<[string, string]>; + + /** + If your string starts with an underscore, it will be preserved in the slugified string. + + Sometimes leading underscores are intentional, for example, filenames representing hidden paths on a website. + + @default false + + @example + ``` + import slugify from '@sindresorhus/slugify'; - slugify('Foo@unicorn', { - customReplacements: [ - ['@', 'at'] - ] - }); - //=> 'fooatunicorn' + slugify('_foo_bar'); + //=> 'foo-bar' - slugify('foo@unicorn', { - customReplacements: [ - ['@', ' at '] - ] - }); - //=> 'foo-at-unicorn' + slugify('_foo_bar', {preserveLeadingUnderscore: true}); + //=> '_foo-bar' + ``` + */ + readonly preserveLeadingUnderscore?: boolean; +} - slugify('I love 🐶', { - customReplacements: [ - ['🐶', 'dogs'] - ] - }); - //=> 'i-love-dogs' - ``` - */ - readonly customReplacements?: ReadonlyArray<[string, string]>; +/** +Slugify a string. - /** - If your string starts with an underscore, it will be preserved in the slugified string. +@param string - String to slugify. - Sometimes leading underscores are intentional, for example, filenames representing hidden paths on a website. +@example +``` +import slugify from '@sindresorhus/slugify'; - @default false +slugify('I ♥ Dogs'); +//=> 'i-love-dogs' - @example - ``` - import slugify = require('@sindresorhus/slugify'); +slugify(' Déjà Vu! '); +//=> 'deja-vu' - slugify('_foo_bar'); - //=> 'foo-bar' +slugify('fooBar 123 $#%'); +//=> 'foo-bar-123' - slugify('_foo_bar', {preserveLeadingUnderscore: true}); - //=> '_foo-bar' - ``` - */ - readonly preserveLeadingUnderscore?: boolean; - } -} +slugify('я люблю единорогов'); +//=> 'ya-lyublyu-edinorogov' +``` +*/ +export default function slugify(string: string, options?: Options): string; + +export interface CountableSlugify { + /** + Reset the counter. + + @example + ``` + import {slugifyWithCounter} from '@sindresorhus/slugify'; + + const slugify = slugifyWithCounter(); + + slugify('foo bar'); + //=> 'foo-bar' + + slugify('foo bar'); + //=> 'foo-bar-2' + + slugify.reset(); + + slugify('foo bar'); + //=> 'foo-bar' + ``` + */ + reset: () => void; -declare const slugify: { /** Returns a new instance of `slugify(string, options?)` with a counter to handle multiple occurences of the same string. @@ -124,18 +169,19 @@ declare const slugify: { @example ``` - import slugify = require('@sindresorhus/slugify'); + import {slugifyWithCounter} from '@sindresorhus/slugify'; + + const slugify = slugifyWithCounter(); - const countableSlugify = slugify.counter(); - countableSlugify('foo bar'); + slugify('foo bar'); //=> 'foo-bar' - countableSlugify('foo bar'); + slugify('foo bar'); //=> 'foo-bar-2' - countableSlugify.reset(); + slugify.reset(); - countableSlugify('foo bar'); + slugify('foo bar'); //=> 'foo-bar' ``` @@ -153,63 +199,9 @@ declare const slugify: { ### Example ``` - You can then use `slugify.counter()` to generate unique HTML `id`'s to ensure anchors will link to the right headline. - */ - counter: () => { - /** - Reset the counter. - - @example - ``` - import slugify = require('@sindresorhus/slugify'); - - const countableSlugify = slugify.counter(); - countableSlugify('foo bar'); - //=> 'foo-bar' - - countableSlugify('foo bar'); - //=> 'foo-bar-2' - - countableSlugify.reset(); - - countableSlugify('foo bar'); - //=> 'foo-bar' - ``` - */ - reset: () => void; - - ( - string: string, - options?: slugify.Options - ): string; - }; - - /** - Slugify a string. - - @param string - String to slugify. - - @example - ``` - import slugify = require('@sindresorhus/slugify'); - - slugify('I ♥ Dogs'); - //=> 'i-love-dogs' - - slugify(' Déjà Vu! '); - //=> 'deja-vu' - - slugify('fooBar 123 $#%'); - //=> 'foo-bar-123' - - slugify('я люблю единорогов'); - //=> 'ya-lyublyu-edinorogov' - ``` + You can then use `slugifyWithCounter()` to generate unique HTML `id`'s to ensure anchors will link to the right headline. */ - ( - string: string, - options?: slugify.Options - ): string; -}; + (string: string, options?: Options): string; +} -export = slugify; +export function slugifyWithCounter(): CountableSlugify; diff --git a/index.js b/index.js index 8584732..b6fcbb0 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,6 @@ -'use strict'; -const escapeStringRegexp = require('escape-string-regexp'); -const transliterate = require('@sindresorhus/transliterate'); -const builtinOverridableReplacements = require('./overridable-replacements'); +import escapeStringRegexp from 'escape-string-regexp'; +import transliterate from '@sindresorhus/transliterate'; +import builtinOverridableReplacements from './overridable-replacements.js'; const decamelize = string => { return string @@ -21,7 +20,7 @@ const removeMootSeparators = (string, separator) => { .replace(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, 'g'), ''); }; -const slugify = (string, options) => { +export default function slugify(string, options) { if (typeof string !== 'string') { throw new TypeError(`Expected a string, got \`${typeof string}\``); } @@ -66,9 +65,9 @@ const slugify = (string, options) => { } return string; -}; +} -const counter = () => { +export function slugifyWithCounter() { const occurrences = new Map(); const countable = (string, options) => { @@ -95,7 +94,4 @@ const counter = () => { }; return countable; -}; - -module.exports = slugify; -module.exports.counter = counter; +} diff --git a/index.test-d.ts b/index.test-d.ts index c8edac0..a6f7af5 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,5 +1,5 @@ import {expectType} from 'tsd'; -import slugify = require('.'); +import slugify, {slugifyWithCounter} from './index.js'; expectType(slugify('I ♥ Dogs')); expectType(slugify('BAR and baz', {separator: '_'})); @@ -9,5 +9,4 @@ expectType(slugify('I ♥ 🦄 & 🐶', {customReplacements: [['🐶', ' expectType(slugify('_foo_bar', {preserveLeadingUnderscore: true})); // Counter -expectType(slugify.counter()('I ♥ Dogs')); -expectType(slugify.counter().reset()); // eslint-disable-line @typescript-eslint/no-invalid-void-type +expectType(slugifyWithCounter()('I ♥ Dogs')); diff --git a/overridable-replacements.js b/overridable-replacements.js index ad1ca76..87f800c 100644 --- a/overridable-replacements.js +++ b/overridable-replacements.js @@ -1,7 +1,7 @@ -'use strict'; - -module.exports = [ +const overridableReplacements = [ ['&', ' and '], ['🦄', ' unicorn '], ['♥', ' love '] ]; + +export default overridableReplacements; diff --git a/package.json b/package.json index 4bf8330..5a85712 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,10 @@ "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, + "type": "module", + "exports": "./index.js", "engines": { - "node": ">=10" + "node": ">=12" }, "scripts": { "test": "xo && ava && tsd" @@ -41,12 +43,12 @@ "id" ], "dependencies": { - "@sindresorhus/transliterate": "^0.1.1", - "escape-string-regexp": "^4.0.0" + "@sindresorhus/transliterate": "^1.0.0", + "escape-string-regexp": "^5.0.0" }, "devDependencies": { - "ava": "^2.4.0", - "tsd": "^0.13.1", - "xo": "^0.32.1" + "ava": "^3.15.0", + "tsd": "^0.14.0", + "xo": "^0.38.2" } } diff --git a/readme.md b/readme.md index 1833a7e..d5c3db8 100644 --- a/readme.md +++ b/readme.md @@ -15,7 +15,7 @@ $ npm install @sindresorhus/slugify ## Usage ```js -const slugify = require('@sindresorhus/slugify'); +import slugify from '@sindresorhus/slugify'; slugify('I ♥ Dogs'); //=> 'i-love-dogs' @@ -50,7 +50,7 @@ Type: `string`\ Default: `'-'` ```js -const slugify = require('@sindresorhus/slugify'); +import slugify from '@sindresorhus/slugify'; slugify('BAR and baz'); //=> 'bar-and-baz' @@ -70,7 +70,7 @@ Default: `true` Make the slug lowercase. ```js -const slugify = require('@sindresorhus/slugify'); +import slugify from '@sindresorhus/slugify'; slugify('Déjà Vu!'); //=> 'deja-vu' @@ -87,7 +87,7 @@ Default: `true` Convert camelcase to separate words. Internally it does `fooBar` → `foo bar`. ```js -const slugify = require('@sindresorhus/slugify'); +import slugify from '@sindresorhus/slugify'; slugify('fooBar'); //=> 'foo-bar' @@ -112,7 +112,7 @@ The replacements are run on the original string before any other transformations This only overrides a default replacement if you set an item with the same key, like `&`. ```js -const slugify = require('@sindresorhus/slugify'); +import slugify from '@sindresorhus/slugify'; slugify('Foo@unicorn', { customReplacements: [ @@ -125,7 +125,7 @@ slugify('Foo@unicorn', { Add a leading and trailing space to the replacement to have it separated by dashes: ```js -const slugify = require('@sindresorhus/slugify'); +import slugify from '@sindresorhus/slugify'; slugify('foo@unicorn', { customReplacements: [ @@ -138,7 +138,7 @@ slugify('foo@unicorn', { Another example: ```js -const slugify = require('@sindresorhus/slugify'); +import slugify from '@sindresorhus/slugify'; slugify('I love 🐶', { customReplacements: [ @@ -158,7 +158,7 @@ If your string starts with an underscore, it will be preserved in the slugified Sometimes leading underscores are intentional, for example, filenames representing hidden paths on a website. ```js -const slugify = require('@sindresorhus/slugify'); +import slugify from '@sindresorhus/slugify'; slugify('_foo_bar'); //=> 'foo-bar' @@ -167,26 +167,26 @@ slugify('_foo_bar', {preserveLeadingUnderscore: true}); //=> '_foo-bar' ``` -### slugify.counter() +### slugifyWithCounter() Returns a new instance of `slugify(string, options?)` with a counter to handle multiple occurences of the same string. #### Example ```js -const slugify = require('@sindresorhus/slugify'); +import {slugifyWithCounter} from '@sindresorhus/slugify'; -const countableSlugify = slugify.counter(); +const slugify = slugifyWithCounter(); -countableSlugify('foo bar'); +slugify('foo bar'); //=> 'foo-bar' -countableSlugify('foo bar'); +slugify('foo bar'); //=> 'foo-bar-2' -countableSlugify.reset(); +slugify.reset(); -countableSlugify('foo bar'); +slugify('foo bar'); //=> 'foo-bar' ``` @@ -204,7 +204,7 @@ If, for example, you have a document with multiple sections where each subsectio ### Example ``` -You can then use `slugify.counter()` to generate unique HTML `id`'s to ensure anchors will link to the right headline. +You can then use `slugifyWithCounter()` to generate unique HTML `id`'s to ensure anchors will link to the right headline. ### slugify.reset() @@ -213,19 +213,19 @@ Reset the counter #### Example ```js -const slugify = require('@sindresorhus/slugify'); +import {slugifyWithCounter} from '@sindresorhus/slugify'; -const countableSlugify = slugify.counter(); +const slugify = slugifyWithCounter(); -countableSlugify('foo bar'); +slugify('foo bar'); //=> 'foo-bar' -countableSlugify('foo bar'); +slugify('foo bar'); //=> 'foo-bar-2' -countableSlugify.reset(); +slugify.reset(); -countableSlugify('foo bar'); +slugify('foo bar'); //=> 'foo-bar' ``` diff --git a/test.js b/test.js index 90b1360..10370a3 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,5 @@ import test from 'ava'; -import slugify from '.'; +import slugify, {slugifyWithCounter} from './index.js'; test('main', t => { t.is(slugify('Foo Bar'), 'foo-bar'); @@ -123,7 +123,7 @@ test('supports Turkish', t => { }); test('supports Armenian', t => { - t.is(slugify('Ե ր ե ւ ա ն', {lowercase: false, separator: ' '}), 're ye v a n'); + t.is(slugify('Ե ր ե ւ ա ն', {lowercase: false, separator: ' '}), 'Ye r ye a n'); }); test('leading underscore', t => { @@ -134,37 +134,37 @@ test('leading underscore', t => { }); test('counter', t => { - const countableSlugify = slugify.counter(); - t.is(countableSlugify('foo bar'), 'foo-bar'); - t.is(countableSlugify('foo bar'), 'foo-bar-2'); - - countableSlugify.reset(); - - t.is(countableSlugify('foo'), 'foo'); - t.is(countableSlugify('foo'), 'foo-2'); - t.is(countableSlugify('foo 1'), 'foo-1'); - t.is(countableSlugify('foo-1'), 'foo-1-2'); - t.is(countableSlugify('foo-1'), 'foo-1-3'); - t.is(countableSlugify('foo'), 'foo-3'); - t.is(countableSlugify('foo'), 'foo-4'); - t.is(countableSlugify('foo-1'), 'foo-1-4'); - t.is(countableSlugify('foo-2'), 'foo-2-1'); - t.is(countableSlugify('foo-2'), 'foo-2-2'); - t.is(countableSlugify('foo-2-1'), 'foo-2-1-1'); - t.is(countableSlugify('foo-2-1'), 'foo-2-1-2'); - t.is(countableSlugify('foo-11'), 'foo-11-1'); - t.is(countableSlugify('foo-111'), 'foo-111-1'); - t.is(countableSlugify('foo-111-1'), 'foo-111-1-1'); - t.is(countableSlugify('fooCamelCase', {lowercase: false, decamelize: false}), 'fooCamelCase'); - t.is(countableSlugify('fooCamelCase', {decamelize: false}), 'foocamelcase-2'); - t.is(countableSlugify('_foo'), 'foo-5'); - t.is(countableSlugify('_foo', {preserveLeadingUnderscore: true}), '_foo'); - t.is(countableSlugify('_foo', {preserveLeadingUnderscore: true}), '_foo-2'); - - const countableSlugify2 = slugify.counter(); - t.is(countableSlugify2('foo'), 'foo'); - t.is(countableSlugify2('foo'), 'foo-2'); - - t.is(countableSlugify2(''), ''); - t.is(countableSlugify2(''), ''); + const slugify = slugifyWithCounter(); + t.is(slugify('foo bar'), 'foo-bar'); + t.is(slugify('foo bar'), 'foo-bar-2'); + + slugify.reset(); + + t.is(slugify('foo'), 'foo'); + t.is(slugify('foo'), 'foo-2'); + t.is(slugify('foo 1'), 'foo-1'); + t.is(slugify('foo-1'), 'foo-1-2'); + t.is(slugify('foo-1'), 'foo-1-3'); + t.is(slugify('foo'), 'foo-3'); + t.is(slugify('foo'), 'foo-4'); + t.is(slugify('foo-1'), 'foo-1-4'); + t.is(slugify('foo-2'), 'foo-2-1'); + t.is(slugify('foo-2'), 'foo-2-2'); + t.is(slugify('foo-2-1'), 'foo-2-1-1'); + t.is(slugify('foo-2-1'), 'foo-2-1-2'); + t.is(slugify('foo-11'), 'foo-11-1'); + t.is(slugify('foo-111'), 'foo-111-1'); + t.is(slugify('foo-111-1'), 'foo-111-1-1'); + t.is(slugify('fooCamelCase', {lowercase: false, decamelize: false}), 'fooCamelCase'); + t.is(slugify('fooCamelCase', {decamelize: false}), 'foocamelcase-2'); + t.is(slugify('_foo'), 'foo-5'); + t.is(slugify('_foo', {preserveLeadingUnderscore: true}), '_foo'); + t.is(slugify('_foo', {preserveLeadingUnderscore: true}), '_foo-2'); + + const slugify2 = slugifyWithCounter(); + t.is(slugify2('foo'), 'foo'); + t.is(slugify2('foo'), 'foo-2'); + + t.is(slugify2(''), ''); + t.is(slugify2(''), ''); });