Skip to content

Commit 334a343

Browse files
authored
feat: seamless automatic fonts (#157)
1 parent 1846c26 commit 334a343

File tree

7 files changed

+164
-89
lines changed

7 files changed

+164
-89
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"husky": "latest",
5050
"jest": "latest",
5151
"nuxt-edge": "latest",
52+
"nuxt-webfontloader": "^1.1.0",
5253
"standard-version": "latest",
5354
"ts-jest": "latest",
5455
"typescript": "~3.5"

src/font.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
import { ModuleThis } from '@nuxt/types/config/module'
22

3-
export default function setupFont (this: ModuleThis) {
4-
this.options.head!.link!.push({
5-
rel: 'stylesheet',
6-
type: 'text/css',
7-
href: `https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900&display=swap`
8-
})
3+
export interface FontOptions {
4+
family?: string
5+
size?: number
6+
}
7+
8+
export default function setupFont (this: ModuleThis, options: FontOptions) {
9+
const family = `${options.family}:100,300,400,500,700,900&display=swap`
10+
11+
if (this.options.modules!.some(mod => mod === 'nuxt-webfontloader')) {
12+
this.options.webfontloader = this.options.webfontloader || {}
13+
this.options.webfontloader.google = this.options.webfontloader.google || {}
14+
this.options.webfontloader.google.families = [...this.options.webfontloader.google.families || [], family]
15+
} else {
16+
this.options.head!.link!.push({
17+
rel: 'stylesheet',
18+
type: 'text/css',
19+
href: `https://fonts.googleapis.com/css?family=${family}`
20+
})
21+
}
22+
23+
// Add font-family custom variable (only if not Roboto, cause already default in Vuetify styles)
24+
if (options.family !== 'Roboto') {
25+
this.options.build!.loaders.sass.prependData = [`$body-font-family: '${options.family}', sans-serif`, this.options.build!.loaders.sass.prependData].join('\n')
26+
}
27+
28+
// Add font-size custom variable
29+
if (options.size) {
30+
this.options.build!.loaders.sass.prependData = [`$font-size-root: ${options.size}px`, this.options.build!.loaders.sass.prependData].join('\n')
31+
}
932
}

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { Framework } from 'vuetify'
33

44
import initOptions, { Options, VuetifyLoaderOptions } from './options'
55
import setupBuild from './build'
6-
import setupIcons from './icons'
76
import setupFont from './font'
7+
import setupIcons from './icons'
88
import setupSass from './sass'
99

1010
declare module '@nuxt/types' {
@@ -22,7 +22,7 @@ const vuetifyModule: Module = function (moduleOptions?: Options) {
2222
const options = initOptions.call(this, moduleOptions)
2323

2424
if (typeof options.defaultAssets === 'object') {
25-
options.defaultAssets.font && setupFont.call(this)
25+
options.defaultAssets.font && setupFont.call(this, options.defaultAssets.font)
2626
options.defaultAssets.icons && setupIcons.call(this, options.defaultAssets.icons)
2727
}
2828

src/options.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { SFCDescriptor } from 'vue-template-compiler'
44
import { VuetifyPreset } from 'vuetify/types/presets'
55
import { ModuleThis } from '@nuxt/types/config/module'
66

7+
import { FontOptions } from './font'
78
import { IconPreset } from './icons'
89

910
export interface VuetifyLoaderOptions {
@@ -18,7 +19,7 @@ export interface VuetifyLoaderOptions {
1819
export interface Options extends Partial<VuetifyPreset> {
1920
customVariables?: string[]
2021
defaultAssets?: {
21-
font?: boolean,
22+
font?: FontOptions,
2223
icons?: IconPreset | false
2324
} | false
2425
optionsPath?: string
@@ -27,11 +28,13 @@ export interface Options extends Partial<VuetifyPreset> {
2728
}
2829
}
2930

30-
const defaults: Options = {
31+
export const defaults = {
3132
customVariables: [],
3233
defaultAssets: {
33-
font: true,
34-
icons: 'mdi'
34+
font: {
35+
family: 'Roboto'
36+
},
37+
icons: 'mdi' as IconPreset
3538
},
3639
optionsPath: undefined,
3740
treeShake: process.env.NODE_ENV === 'production'
@@ -41,7 +44,7 @@ export default function initOptions (this: ModuleThis, moduleOptions?: Options):
4144
const options = merge.all([
4245
defaults,
4346
this.options.vuetify || {},
44-
moduleOptions!
47+
moduleOptions || {}
4548
]) as Required<Options>
4649

4750
return options

src/sass.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ export default function setupSass (this: ModuleThis, options: Pick<Options, 'cus
77
// Cause since loader options validation, this will fail: https://github.com/nuxt/nuxt.js/tree/c8ee9a660809e856c28d8678c6a632bbdd6ed00f/packages/config/src/config/build.js#L50
88
delete this.options.build!.loaders.sass.indentedSyntax
99

10+
// Use Dart Sass
1011
this.options.build!.loaders.sass.implementation =
1112
this.options.build!.loaders.scss.implementation =
1213
dartSass
1314

1415
// Custom variables
15-
const sassLoaderData: string | Function = this.options.build!.loaders.sass.prependData
16-
17-
if (options.customVariables && options.customVariables.length > 0 && typeof sassLoaderData !== 'function') {
16+
if (options.customVariables && options.customVariables.length > 0) {
1817
const imports = options.customVariables.map(path => `@import '${path}'`).join('\n')
19-
this.options.build!.loaders.sass.prependData = sassLoaderData ? sassLoaderData.concat('\n', imports) : imports
18+
this.options.build!.loaders.sass.prependData = [this.options.build!.loaders.sass.prependData, imports].join('\n')
2019
}
2120
}

test/module.test.ts

Lines changed: 109 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,159 @@
11
import { Nuxt } from '@nuxt/core-edge'
2-
import { Builder } from '@nuxt/builder-edge'
3-
import { BundleBuilder } from '@nuxt/webpack-edge'
4-
import { Configuration } from '@nuxt/types'
2+
import dartSass from 'sass'
53
import VuetifyLoaderPlugin from 'vuetify-loader/lib/plugin'
64

7-
import vuetifyModule, { VuetifyLoaderOptions } from '../src'
5+
import _vuetifyModule from '../src'
6+
import _initOptions, { defaults as defaultOptions, Options, VuetifyLoaderOptions } from '../src/options'
7+
import _setupBuild from '../src/build'
8+
import _setupFont, { FontOptions } from '../src/font'
9+
import _setupIcons, { IconPreset } from '../src/icons'
10+
import _setupSass from '../src/sass'
811

9-
jest.setTimeout(60000)
1012
jest.mock('vuetify-loader/lib/plugin')
1113

12-
const buildWithVuetifyModule = async (config: Partial<Configuration> = {}) => {
13-
const nuxt = new Nuxt({
14-
buildModules: [vuetifyModule],
15-
...config
16-
} as Configuration)
14+
let nuxt
1715

18-
try {
19-
await nuxt.ready()
20-
await new Builder(nuxt, BundleBuilder).build()
21-
} catch (err) {
16+
const vuetifyModule = async (options?: Options) => {
17+
_vuetifyModule.call(nuxt.moduleContainer, options)
18+
await nuxt.callHook('build:before')
19+
}
20+
const initOptions = (options?: Options): Required<Options> => _initOptions.call(nuxt.moduleContainer, options)
21+
const setupBuild = (options?: Options) => {
22+
_setupBuild.call(nuxt.moduleContainer, options)
23+
nuxt.options.build.extend && nuxt.options.build.extend({ plugins: [] })
24+
}
25+
const setupFont = (options?: FontOptions) => _setupFont.call(nuxt.moduleContainer, options)
26+
const setupIcons = (preset?: IconPreset) => _setupIcons.call(nuxt.moduleContainer, preset)
27+
const setupSass = (options?: Options) => _setupSass.call(nuxt.moduleContainer, options)
2228

23-
}
29+
beforeEach(async () => {
30+
nuxt = new Nuxt()
31+
await nuxt.ready()
32+
})
2433

25-
return nuxt
26-
}
34+
describe('initOptions', () => {
35+
test('default', () => {
36+
const options = initOptions()
2737

28-
describe('module', () => {
29-
let nuxt
38+
expect(options).toEqual(defaultOptions)
39+
})
40+
})
3041

31-
beforeEach(() => {
32-
jest.clearAllMocks()
42+
describe('setupFont', () => {
43+
test('default', () => {
44+
setupFont(defaultOptions.defaultAssets.font)
45+
46+
expect(nuxt.options.head.link).toContainEqual({
47+
rel: 'stylesheet',
48+
type: 'text/css',
49+
href: `https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900&display=swap`
50+
})
3351
})
3452

35-
test('with default options', async () => {
36-
nuxt = await buildWithVuetifyModule({
37-
dir: {
38-
app: ''
53+
test('with nuxt-webfontloader', () => {
54+
nuxt.options.modules = ['nuxt-webfontloader']
55+
56+
setupFont({
57+
family: 'Montserrat',
58+
size: 20 }
59+
)
60+
61+
expect(nuxt.options.webfontloader).toEqual({
62+
google: {
63+
families: ['Montserrat:100,300,400,500,700,900&display=swap']
3964
}
4065
})
4166

42-
expect(nuxt.options.head.link).toHaveLength(2)
43-
expect(nuxt.options.build.templates).toHaveLength(2)
44-
expect(nuxt.options.build.templates.map(t => t.dst)).toEqual(['vuetify/options.js', 'vuetify/plugin.js'])
67+
expect(nuxt.options.build.loaders.sass.prependData).toContain("$body-font-family: 'Montserrat', sans-serif")
68+
expect(nuxt.options.build.loaders.sass.prependData).toContain('$font-size-root: 20px')
4569
})
70+
})
4671

47-
test('without defaultAssets', async () => {
48-
nuxt = await buildWithVuetifyModule({
49-
vuetify: {
50-
defaultAssets: false
51-
}
72+
describe('setupIcons', () => {
73+
test('default', () => {
74+
setupIcons(defaultOptions.defaultAssets.icons)
75+
76+
expect(nuxt.options.head.link).toContainEqual({
77+
rel: 'stylesheet',
78+
type: 'text/css',
79+
href: 'https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css'
5280
})
81+
})
82+
})
5383

54-
expect(nuxt.options.head.link).toHaveLength(0)
84+
describe('setupSass', () => {
85+
test('default', () => {
86+
setupSass(defaultOptions)
87+
88+
expect(nuxt.options.build.loaders.sass.indentedSyntax).toBeUndefined()
89+
expect(nuxt.options.build.loaders.sass.implementation).toEqual(dartSass)
90+
expect(nuxt.options.build.loaders.scss.implementation).toEqual(dartSass)
5591
})
5692

57-
test('with customVariables', async () => {
58-
nuxt = await buildWithVuetifyModule({
59-
vuetify: {
60-
customVariables: ['/path/to/variables.scss']
61-
}
93+
test('customVariables', () => {
94+
setupSass({
95+
customVariables: ['/path/to/variables.scss']
6296
})
6397

64-
expect(nuxt.options.build.loaders.sass.prependData).toEqual("@import '/path/to/variables.scss'")
98+
expect(nuxt.options.build.loaders.sass.prependData).toContain("@import '/path/to/variables.scss'")
6599
})
100+
})
66101

67-
test('with customVariables (with existing prependData)', async () => {
68-
nuxt = await buildWithVuetifyModule({
69-
build: {
70-
loaders: {
71-
sass: {
72-
prependData: '$someVariable: #000000'
73-
}
74-
}
75-
},
76-
vuetify: {
77-
customVariables: ['/path/to/variables.scss']
78-
}
79-
})
102+
describe('setupBuild', () => {
103+
test('default', () => {
104+
nuxt.options.dir.app = ''
80105

81-
expect(nuxt.options.build.loaders.sass.prependData).toEqual("$someVariable: #000000\n@import '/path/to/variables.scss'")
106+
setupBuild(defaultOptions)
107+
108+
expect(nuxt.options.css).toContain('vuetify/dist/vuetify.css')
109+
expect(nuxt.options.build.templates.map(t => t.dst)).toEqual(['vuetify/options.js', 'vuetify/plugin.js'])
82110
})
83111

84-
test('with optionsPath', async () => {
85-
nuxt = await buildWithVuetifyModule({
86-
vuetify: {
87-
optionsPath: 'test/fixture/vuetify.options.ts'
88-
}
112+
test('optionsPath', () => {
113+
setupBuild({
114+
...defaultOptions,
115+
optionsPath: 'test/fixture/vuetify.options.ts'
89116
})
90117

91118
expect(nuxt.options.build.templates.map(t => t.dst)).toContain('vuetify/options.ts')
92119
})
93120

94-
test('with treeShake', async () => {
95-
nuxt = await buildWithVuetifyModule({
96-
vuetify: {
97-
treeShake: true
98-
}
121+
test('treeShake', () => {
122+
setupBuild({
123+
treeShake: true
99124
})
100125

101126
expect(nuxt.options.build.transpile).toContain('vuetify/lib')
102-
expect(VuetifyLoaderPlugin).toHaveBeenCalledTimes(2) // client (1) + server (1) = (2)
127+
expect(VuetifyLoaderPlugin).toHaveBeenCalled()
103128
})
104129

105-
test('with treeShake (and loaderOptions)', async () => {
130+
test('treeShake with loaderOptions', () => {
106131
const loaderOptions: VuetifyLoaderOptions = {
107132
match () {
108133
return []
109134
}
110135
}
111136

112-
nuxt = await buildWithVuetifyModule({
113-
vuetify: {
114-
treeShake: {
115-
loaderOptions
116-
}
137+
setupBuild({
138+
treeShake: {
139+
loaderOptions
117140
}
118141
})
119142

120-
expect(VuetifyLoaderPlugin).toHaveBeenNthCalledWith(2, loaderOptions)
143+
expect(nuxt.options.build.transpile).toContain('vuetify/lib')
144+
expect(VuetifyLoaderPlugin).toHaveBeenCalledWith(loaderOptions)
145+
})
146+
})
147+
148+
describe('module', () => {
149+
test('default', async () => {
150+
await vuetifyModule(defaultOptions)
151+
})
152+
153+
test('without defaultAssets', async () => {
154+
await vuetifyModule({
155+
...defaultOptions,
156+
defaultAssets: false
157+
})
121158
})
122159
})

yarn.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7289,6 +7289,13 @@ nuxt-edge@latest:
72897289
"@nuxt/opencollective" "^0.3.0"
72907290
"@nuxt/webpack-edge" "2.10.0-26128809.376b2b07"
72917291

7292+
nuxt-webfontloader@^1.1.0:
7293+
version "1.1.0"
7294+
resolved "https://registry.yarnpkg.com/nuxt-webfontloader/-/nuxt-webfontloader-1.1.0.tgz#68b47ffbeaee4c41969f42f57a86946e0c36715c"
7295+
integrity sha512-GyDgABmI0Oq54s2tA9SZC28TmHy2xGdWSXrfcGPPfDBVhgQQlGL5CJcAlvovcuhefzzZrzGgs35HIcv5qym4fQ==
7296+
dependencies:
7297+
webfontloader "^1.6.28"
7298+
72927299
nwsapi@^2.0.7:
72937300
version "2.1.4"
72947301
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.4.tgz#e006a878db23636f8e8a67d33ca0e4edf61a842f"
@@ -10582,6 +10589,11 @@ watchpack@^1.6.0:
1058210589
graceful-fs "^4.1.2"
1058310590
neo-async "^2.5.0"
1058410591

10592+
webfontloader@^1.6.28:
10593+
version "1.6.28"
10594+
resolved "https://registry.yarnpkg.com/webfontloader/-/webfontloader-1.6.28.tgz#db786129253cb6e8eae54c2fb05f870af6675bae"
10595+
integrity sha1-23hhKSU8tujq5UwvsF+HCvZnW64=
10596+
1058510597
webidl-conversions@^4.0.2:
1058610598
version "4.0.2"
1058710599
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"

0 commit comments

Comments
 (0)