This repository has been archived by the owner on May 1, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 305
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(postprocess): fix and add tests for the logic surrounding purging…
… fonts
- Loading branch information
1 parent
fbad33f
commit 0dd1b22
Showing
8 changed files
with
228 additions
and
106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"name": "@ionic/app-scripts", | ||
"version": "2.1.4-201709061759", | ||
"version": "2.1.4", | ||
"description": "Scripts for Ionic Projects", | ||
"homepage": "https://ionicframework.com/", | ||
"author": "Ionic Team <[email protected]> (https://ionic.io)", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { join } from 'path'; | ||
|
||
import { removeUnusedFonts } from './remove-unused-fonts'; | ||
import * as helpers from '../util/helpers'; | ||
|
||
describe('Remove Fonts', () => { | ||
describe('removeUnusedFonts', () => { | ||
it('should not purge any fonts when target is not cordova', () => { | ||
const fakeFontDirPath = join(process.cwd(), 'www', 'assets', 'fonts'); | ||
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(fakeFontDirPath); | ||
spyOn(helpers, helpers.readDirAsync.name).and.returnValue(Promise.resolve(getMockFontDirData())); | ||
spyOn(helpers, helpers.unlinkAsync.name).and.returnValue(Promise.resolve()); | ||
|
||
return removeUnusedFonts({ target: 'notCordova', platform: 'web' }).then(() => { | ||
expect(helpers.getStringPropertyValue).toHaveBeenCalled(); | ||
expect(helpers.readDirAsync).toHaveBeenCalledWith(fakeFontDirPath); | ||
expect(helpers.unlinkAsync).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
|
||
it('should purge all non-woffs for ionicons and roboto, and then all of noto-sans for ios', () => { | ||
const fakeFontDirPath = join(process.cwd(), 'www', 'assets', 'fonts'); | ||
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(fakeFontDirPath); | ||
spyOn(helpers, helpers.readDirAsync.name).and.returnValue(Promise.resolve(getMockFontDirData())); | ||
const unlinkSpy = spyOn(helpers, helpers.unlinkAsync.name).and.returnValue(Promise.resolve()); | ||
|
||
return removeUnusedFonts({ target: 'cordova', platform: 'ios' }).then(() => { | ||
expect(helpers.readDirAsync).toHaveBeenCalledWith(fakeFontDirPath); | ||
expect(unlinkSpy.calls.all()[0].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.eot')); | ||
expect(unlinkSpy.calls.all()[1].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.scss')); | ||
expect(unlinkSpy.calls.all()[2].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.svg')); | ||
expect(unlinkSpy.calls.all()[3].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.ttf')); | ||
expect(unlinkSpy.calls.all()[4].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-bold.ttf')); | ||
expect(unlinkSpy.calls.all()[5].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-bold.woff')); | ||
expect(unlinkSpy.calls.all()[6].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-regular.ttf')); | ||
expect(unlinkSpy.calls.all()[7].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-regular.woff')); | ||
expect(unlinkSpy.calls.all()[8].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans.scss')); | ||
|
||
expect(unlinkSpy.calls.all()[9].args[0]).toEqual(join(fakeFontDirPath, 'roboto-bold.ttf')); | ||
expect(unlinkSpy.calls.all()[10].args[0]).toEqual(join(fakeFontDirPath, 'roboto-light.ttf')); | ||
expect(unlinkSpy.calls.all()[11].args[0]).toEqual(join(fakeFontDirPath, 'roboto-medium.ttf')); | ||
expect(unlinkSpy.calls.all()[12].args[0]).toEqual(join(fakeFontDirPath, 'roboto-regular.ttf')); | ||
expect(unlinkSpy.calls.all()[13].args[0]).toEqual(join(fakeFontDirPath, 'roboto.scss')); | ||
}); | ||
}); | ||
|
||
it('should purge all non-woffs for ionicons, all of roboto and noto-sans for android', () => { | ||
const fakeFontDirPath = join(process.cwd(), 'www', 'assets', 'fonts'); | ||
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(fakeFontDirPath); | ||
spyOn(helpers, helpers.readDirAsync.name).and.returnValue(Promise.resolve(getMockFontDirData())); | ||
const unlinkSpy = spyOn(helpers, helpers.unlinkAsync.name).and.returnValue(Promise.resolve()); | ||
|
||
return removeUnusedFonts({ target: 'cordova', platform: 'android' }).then(() => { | ||
expect(helpers.readDirAsync).toHaveBeenCalledWith(fakeFontDirPath); | ||
expect(unlinkSpy.calls.all()[0].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.eot')); | ||
expect(unlinkSpy.calls.all()[1].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.scss')); | ||
expect(unlinkSpy.calls.all()[2].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.svg')); | ||
expect(unlinkSpy.calls.all()[3].args[0]).toEqual(join(fakeFontDirPath, 'ionicons.ttf')); | ||
expect(unlinkSpy.calls.all()[4].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-bold.ttf')); | ||
expect(unlinkSpy.calls.all()[5].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-bold.woff')); | ||
expect(unlinkSpy.calls.all()[6].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-regular.ttf')); | ||
expect(unlinkSpy.calls.all()[7].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans-regular.woff')); | ||
expect(unlinkSpy.calls.all()[8].args[0]).toEqual(join(fakeFontDirPath, 'noto-sans.scss')); | ||
|
||
expect(unlinkSpy.calls.all()[9].args[0]).toEqual(join(fakeFontDirPath, 'roboto-bold.ttf')); | ||
expect(unlinkSpy.calls.all()[10].args[0]).toEqual(join(fakeFontDirPath, 'roboto-bold.woff')); | ||
expect(unlinkSpy.calls.all()[11].args[0]).toEqual(join(fakeFontDirPath, 'roboto-bold.woff2')); | ||
expect(unlinkSpy.calls.all()[12].args[0]).toEqual(join(fakeFontDirPath, 'roboto-light.ttf')); | ||
expect(unlinkSpy.calls.all()[13].args[0]).toEqual(join(fakeFontDirPath, 'roboto-light.woff')); | ||
expect(unlinkSpy.calls.all()[14].args[0]).toEqual(join(fakeFontDirPath, 'roboto-light.woff2')); | ||
expect(unlinkSpy.calls.all()[15].args[0]).toEqual(join(fakeFontDirPath, 'roboto-medium.ttf')); | ||
expect(unlinkSpy.calls.all()[16].args[0]).toEqual(join(fakeFontDirPath, 'roboto-medium.woff')); | ||
expect(unlinkSpy.calls.all()[17].args[0]).toEqual(join(fakeFontDirPath, 'roboto-medium.woff2')); | ||
expect(unlinkSpy.calls.all()[18].args[0]).toEqual(join(fakeFontDirPath, 'roboto-regular.ttf')); | ||
expect(unlinkSpy.calls.all()[19].args[0]).toEqual(join(fakeFontDirPath, 'roboto-regular.woff')); | ||
expect(unlinkSpy.calls.all()[20].args[0]).toEqual(join(fakeFontDirPath, 'roboto-regular.woff2')); | ||
expect(unlinkSpy.calls.all()[21].args[0]).toEqual(join(fakeFontDirPath, 'roboto.scss')); | ||
|
||
}); | ||
}); | ||
|
||
it('should purge all non-woffs for ionicons, all of roboto and noto-sans for windows', () => { | ||
const fakeFontDirPath = join(process.cwd(), 'www', 'assets', 'fonts'); | ||
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(fakeFontDirPath); | ||
spyOn(helpers, helpers.readDirAsync.name).and.returnValue(Promise.resolve(getMockFontDirData())); | ||
const unlinkSpy = spyOn(helpers, helpers.unlinkAsync.name).and.returnValue(Promise.resolve()); | ||
|
||
return removeUnusedFonts({ target: 'cordova', platform: 'windows' }).then(() => { | ||
expect(helpers.readDirAsync).toHaveBeenCalledWith(fakeFontDirPath); | ||
expect(helpers.unlinkAsync).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
function getMockFontDirData() { | ||
return [ | ||
'ionicons.eot', | ||
'ionicons.scss', | ||
'ionicons.svg', | ||
'ionicons.ttf', | ||
'ionicons.woff', | ||
'ionicons.woff2', | ||
'noto-sans-bold.ttf', | ||
'noto-sans-bold.woff', | ||
'noto-sans-regular.ttf', | ||
'noto-sans-regular.woff', | ||
'noto-sans.scss', | ||
'roboto-bold.ttf', | ||
'roboto-bold.woff', | ||
'roboto-bold.woff2', | ||
'roboto-light.ttf', | ||
'roboto-light.woff', | ||
'roboto-light.woff2', | ||
'roboto-medium.ttf', | ||
'roboto-medium.woff', | ||
'roboto-medium.woff2', | ||
'roboto-regular.ttf', | ||
'roboto-regular.woff', | ||
'roboto-regular.woff2', | ||
'roboto.scss', | ||
'my-custom-font.eot', | ||
'my-custom-font.scss', | ||
'my-custom-font.svg', | ||
'my-custom-font.ttf', | ||
'my-custom-font.woff', | ||
'my-custom-font.woff2' | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,91 +1,68 @@ | ||
import { BuildContext } from '../util/interfaces'; | ||
import { join } from 'path'; | ||
import { Logger } from '../logger/logger'; | ||
import { unlinkAsync } from '../util/helpers'; | ||
import * as glob from 'glob'; | ||
|
||
|
||
export function removeUnusedFonts(context: BuildContext) { | ||
// For webapps, we pretty much need all fonts to be available because | ||
// the web server deployment never knows which browser/platform is | ||
// opening the app. Additionally, webapps will request fonts on-demand, | ||
// so having them all sit in the www/assets/fonts directory doesn’t | ||
// hurt anything if it’s never being requested. | ||
|
||
// However, with Cordova, the entire directory gets bundled and | ||
// shipped in the ipa/apk, but we also know exactly which platform | ||
// is opening the webapp. For this reason we can safely delete font | ||
// files we know would never be opened by the platform. So app-scripts | ||
// will continue to copy all font files over, but the cordova build | ||
// process would delete those we know are useless and just taking up | ||
// space. End goal is that the Cordova ipa/apk filesize is smaller. | ||
import { extname, join } from 'path'; | ||
|
||
// Font Format Support: | ||
// ttf: http://caniuse.com/#feat=ttf | ||
// woff: http://caniuse.com/#feat=woff | ||
// woff2: http://caniuse.com/#feat=woff2 | ||
|
||
if (context.target === 'cordova') { | ||
const fontsRemoved: string[] = []; | ||
// all cordova builds should remove .eot, .svg, .ttf, and .scss files | ||
fontsRemoved.push('*.eot'); | ||
fontsRemoved.push('*.ttf'); | ||
fontsRemoved.push('*.svg'); | ||
fontsRemoved.push('*.scss'); | ||
import { Logger } from '../logger/logger'; | ||
import * as Constants from '../util/constants'; | ||
import { getStringPropertyValue, readDirAsync, unlinkAsync } from '../util/helpers'; | ||
import { BuildContext } from '../util/interfaces'; | ||
|
||
// all cordova builds should remove Noto-Sans | ||
// Only windows would use Noto-Sans, and it already comes with | ||
// a system font so it wouldn't need our own copy. | ||
fontsRemoved.push('noto-sans*'); | ||
|
||
if (context.platform === 'android') { | ||
// Remove all Roboto fonts. Android already comes with Roboto system | ||
// fonts so shipping our own is unnecessary. Including roboto fonts | ||
// is only useful for PWAs and during development. | ||
fontsRemoved.push('roboto*'); | ||
// For webapps, we pretty much need all fonts to be available because | ||
// the web server deployment never knows which browser/platform is | ||
// opening the app. Additionally, webapps will request fonts on-demand, | ||
// so having them all sit in the www/assets/fonts directory doesn’t | ||
// hurt anything if it’s never being requested. | ||
|
||
// However, with Cordova, the entire directory gets bundled and | ||
// shipped in the ipa/apk, but we also know exactly which platform | ||
// is opening the webapp. For this reason we can safely delete font | ||
// files we know would never be opened by the platform. So app-scripts | ||
// will continue to copy all font files over, but the cordova build | ||
// process would delete those we know are useless and just taking up | ||
// space. End goal is that the Cordova ipa/apk filesize is smaller. | ||
|
||
// Font Format Support: | ||
// ttf: http://caniuse.com/#feat=ttf | ||
// woff: http://caniuse.com/#feat=woff | ||
// woff2: http://caniuse.com/#feat=woff2 | ||
export function removeUnusedFonts(context: BuildContext): Promise<any> { | ||
const fontDir = getStringPropertyValue(Constants.ENV_VAR_FONTS_DIR); | ||
return readDirAsync(fontDir).then((fileNames: string[]) => { | ||
fileNames = fileNames.sort(); | ||
const toPurge = getFontFileNamesToPurge(context.target, context.platform, fileNames); | ||
const fullPaths = toPurge.map(fileName => join(fontDir, fileName)); | ||
const promises = fullPaths.map(fullPath => unlinkAsync(fullPath)); | ||
return Promise.all(promises); | ||
}); | ||
} | ||
|
||
} else if (context.platform === 'ios') { | ||
// Keep Roboto for now. Apps built for iOS may still use Material Design, | ||
// so in that case Roboto should be available. Later we can improve the | ||
// CLI to be smarter and read the user’s ionic config. Also, the roboto | ||
// fonts themselves are pretty small. | ||
export function getFontFileNamesToPurge(target: string, platform: string, fileNames: string[]): string[] { | ||
if (target !== Constants.CORDOVA) { | ||
return []; | ||
} | ||
const filesToDelete = new Set<string>(); | ||
for (const fileName of fileNames) { | ||
if (platform === 'android') { | ||
// remove noto-sans, roboto, and non-woff ionicons | ||
if (fileName.startsWith('noto-sans') || fileName.startsWith('roboto') || (isIonicons(fileName) && !isWoof(fileName))) { | ||
filesToDelete.add(fileName); | ||
} | ||
} else if (platform === 'ios') { | ||
// remove noto-sans, non-woff ionicons | ||
if (fileName.startsWith('noto-sans') || (fileName.startsWith('roboto') && !isWoof(fileName)) || (isIonicons(fileName) && !isWoof(fileName))) { | ||
filesToDelete.add(fileName); | ||
} | ||
} | ||
// for now don't bother deleting anything for windows, need to get some info first | ||
|
||
let filesToDelete: string[] = []; | ||
|
||
let promises = fontsRemoved.map(pattern => { | ||
return new Promise(resolve => { | ||
let searchPattern = join(context.wwwDir, 'assets', 'fonts', pattern); | ||
|
||
glob(searchPattern, (err, files) => { | ||
if (err) { | ||
Logger.error(`removeUnusedFonts: ${err}`); | ||
|
||
} else { | ||
files.forEach(f => { | ||
if (filesToDelete.indexOf(f) === -1) { | ||
filesToDelete.push(f); | ||
} | ||
}); | ||
} | ||
|
||
resolve(); | ||
}); | ||
|
||
}); | ||
}); | ||
|
||
return Promise.all(promises).then(() => { | ||
return unlinkAsync(filesToDelete).then(() => { | ||
if (filesToDelete.length) { | ||
Logger.info(`removed unused font files`); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
}); | ||
} | ||
return Array.from(filesToDelete); | ||
} | ||
|
||
function isIonicons(fileName: string) { | ||
return fileName.startsWith('ionicons'); | ||
} | ||
|
||
// nothing to do here, carry on | ||
return Promise.resolve(); | ||
// woof woof | ||
function isWoof(fileName: string) { | ||
return extname(fileName) === '.woff' || extname(fileName) === '.woff2'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.