Skip to content

Commit

Permalink
fix(publicPath): allow for custom public path
Browse files Browse the repository at this point in the history
Closes #464
  • Loading branch information
adamdbradley committed Feb 12, 2018
1 parent b8abbce commit 19095e7
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 66 deletions.
4 changes: 2 additions & 2 deletions scripts/build-loader.js
Expand Up @@ -20,11 +20,11 @@ let content = fs.readFileSync(srcLoaderPath, 'utf-8');

content = content.replace(/export function /g, 'function ');

content = `(function(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCoreSsr, appCorePolyfilled, hydratedCssClass, components) {
content = `(function(win, doc, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCoreSsr, appCorePolyfilled, hydratedCssClass, components) {
${content}
init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCoreSsr, appCorePolyfilled, hydratedCssClass, components);
init(win, doc, doc.scripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCoreSsr, appCorePolyfilled, hydratedCssClass, components);
})(window, document, '__APP__');`;

Expand Down
12 changes: 8 additions & 4 deletions src/client/loader.ts
Expand Up @@ -4,9 +4,11 @@ import { LoadComponentRegistry } from '../declarations';
export function init(
win: any,
doc: HTMLDocument,
docScripts: HTMLScriptElement[],
appNamespace: string,
urlNamespace: string,
publicPath: string,
discoverPublicPath: boolean,
appCore: string,
appCorePolyfilled: string,
hydratedCssClass: string,
Expand All @@ -27,10 +29,12 @@ export function init(

// get this current script
// script tag cannot use "async" attribute
x = doc.scripts[doc.scripts.length - 1];
if (x && x.src) {
y = x.src.split('/').slice(0, -1);
publicPath = (y.join('/')) + (y.length ? '/' : '') + urlNamespace + '/';
if (discoverPublicPath) {
x = docScripts[docScripts.length - 1];
if (x && x.src) {
y = x.src.split('/').slice(0, -1);
publicPath = (y.join('/')) + (y.length ? '/' : '') + urlNamespace + '/';
}
}

// request the core this browser needs
Expand Down
68 changes: 56 additions & 12 deletions src/client/test/loader.spec.ts
Expand Up @@ -7,9 +7,11 @@ describe('loader', () => {

let win: any;
let doc: HTMLDocument;
let docScripts: HTMLScriptElement[];
let appNamespace: string;
let urlNamespace: string;
let publicPath: string;
let discoverPublicPath: boolean;
let appCore: string;
let appCorePolyfilled: string;
let hydratedCssClass: string;
Expand All @@ -18,9 +20,11 @@ describe('loader', () => {
beforeEach(() => {
win = mockWindow();
doc = win.document;
docScripts = doc.scripts as any;
appNamespace = 'AppNameSpace';
urlNamespace = 'app-namespace';
publicPath = '/build/app-namespace/';
discoverPublicPath = true;
appCore = 'app.core.js';
appCorePolyfilled = 'app.core.pf.js';
hydratedCssClass = 'hydrated';
Expand All @@ -29,21 +33,61 @@ describe('loader', () => {

describe('init', () => {

describe('publicPath', () => {

it('should not discover public path, but always use given path', () => {
const script1 = doc.createElement('script');
script1.src = '/assets1/script1/file.js';

const script2 = doc.createElement('script');
script2.src = '/assets2/script2/file.js';

discoverPublicPath = false;
publicPath = '/my-awesome-public/path/';

docScripts = [
script1,
script2
];
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const script = doc.head.children[0];
expect(script.getAttribute('data-path')).toBe('/my-awesome-public/path/');
});

it('should discover public path from last script', () => {
const script1 = doc.createElement('script');
script1.src = '/assets1/script1/file.js';

const script2 = doc.createElement('script');
script2.src = '/assets2/script2/file.js';

discoverPublicPath = true;
docScripts = [
script1,
script2
];
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const script = doc.head.children[0];
expect(script.getAttribute('data-path')).toBe('/assets2/script2/app-namespace/');
});

});

it('set window namespace', () => {
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
expect(win[appNamespace]).toBeDefined();
});

it('set window namespace components', () => {
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
expect(win[appNamespace].components).toBe(components);
});

it('add <style> when components w/ styles', () => {
components = [
['cmp-tag', {}, true] as any
];
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const style = doc.head.querySelector('style');
expect(style.hasAttribute('data-styles')).toBeTruthy();
expect(style.innerHTML.indexOf('cmp-tag') > -1).toBeTruthy();
Expand All @@ -52,25 +96,25 @@ describe('loader', () => {

it('do not add <style> when no components w/ styles', () => {
components = [];
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const style = doc.head.querySelector('style');
expect(style).toBeFalsy();
});

it('set script src attribute', () => {
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const script = doc.head.children[0];
expect(script.getAttribute('src')).toBe('/build/app-namespace/app.core.pf.js');
});

it('set script public path data attribute', () => {
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const script = doc.head.children[0];
expect(script.getAttribute('data-path')).toBe(publicPath);
});

it('set script appNamespace data attribute', () => {
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const script = doc.head.children[0];
expect(script.getAttribute('data-namespace')).toBe(urlNamespace);
});
Expand All @@ -79,7 +123,7 @@ describe('loader', () => {
const scriptElm = doc.createElement('script');
scriptElm.src = 'file:///c:/path/to/my%20bundle.js';
doc.head.appendChild(scriptElm);
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const script = doc.head.lastElementChild;
expect(script.getAttribute('src')).toBe('file:///c:/path/to/app-namespace/app.core.pf.js');
});
Expand All @@ -88,7 +132,7 @@ describe('loader', () => {
const scriptElm = doc.createElement('script');
scriptElm.src = 'http://domain.com/some/path/bundle.js';
doc.head.appendChild(scriptElm);
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const script = doc.head.lastElementChild;
expect(script.getAttribute('src')).toBe('http://domain.com/some/path/app-namespace/app.core.pf.js');
});
Expand All @@ -97,7 +141,7 @@ describe('loader', () => {
const scriptElm = doc.createElement('script');
scriptElm.src = '../bundle.js';
doc.head.appendChild(scriptElm);
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const script = doc.head.lastElementChild;
expect(script.getAttribute('src')).toBe('../app-namespace/app.core.pf.js');
});
Expand All @@ -106,7 +150,7 @@ describe('loader', () => {
const scriptElm = doc.createElement('script');
scriptElm.src = './bundle.js';
doc.head.appendChild(scriptElm);
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const script = doc.head.lastElementChild;
expect(script.getAttribute('src')).toBe('./app-namespace/app.core.pf.js');
});
Expand All @@ -115,7 +159,7 @@ describe('loader', () => {
const scriptElm = doc.createElement('script');
scriptElm.src = 'bundle.js';
doc.head.appendChild(scriptElm);
loader.init(win, doc, appNamespace, urlNamespace, publicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
loader.init(win, doc, docScripts, appNamespace, urlNamespace, publicPath, discoverPublicPath, appCore, appCorePolyfilled, hydratedCssClass, components);
const script = doc.head.lastElementChild;
expect(script.getAttribute('src')).toBe('app-namespace/app.core.pf.js');
});
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/app/app-file-naming.ts
Expand Up @@ -75,5 +75,9 @@ export function getBundleFilename(bundleId: string, isScopedStyles: boolean, sou


export function getAppPublicPath(config: Config) {
return pathJoin(config, config.publicPath, config.fsNamespace) + '/';
if (config.discoverPublicPath !== false) {
return pathJoin(config, config.publicPath, config.fsNamespace) + '/';
}

return config.publicPath;
}
3 changes: 3 additions & 0 deletions src/compiler/app/app-loader.ts
Expand Up @@ -79,10 +79,13 @@ export function injectAppIntoLoader(

const publicPath = getAppPublicPath(config);

const discoverPublicPath = (config.discoverPublicPath !== false);

const loaderArgs = [
`"${config.namespace}"`,
`"${config.fsNamespace}"`,
`"${publicPath}"`,
`${discoverPublicPath}`,
`"${appCoreFileName}"`,
`"${appCorePolyfilledFileName}"`,
`"${hydratedCssClass}"`,
Expand Down
34 changes: 32 additions & 2 deletions src/compiler/app/test/app-loader.spec.ts
Expand Up @@ -47,7 +47,7 @@ describe('build-project-files', () => {
`("__APP__")`
);

expect(appLoader).toBe(`("MyApp","my-app","build/my-app/","my-app.core.js","my-app.core.pf.js","hydrated-css",[["root-cmp",{"Mode1":"abc","Mode2":"def"}]])`);
expect(appLoader).toBe(`("MyApp","my-app","build/my-app/",true,"my-app.core.js","my-app.core.pf.js","hydrated-css",[["root-cmp",{"Mode1":"abc","Mode2":"def"}]])`);
});

});
Expand All @@ -67,12 +67,42 @@ describe('build-project-files', () => {
expect(mockGetClientCoreFile.mock.calls[0][0]).toEqual({ staticName: 'loader.js' });
});

it('includes the injected app, w/ discoverPublicPath', async () => {
mockGetClientCoreFile.mockReturnValue(Promise.resolve(`pretend i am code ('__APP__') yeah me too`));

const ctx: CompilerCtx = { appFiles: {}, cache: mockCache() as any };

const appRegistry: AppRegistry = {
core: 'myapp.core.js',
corePolyfilled: 'myapp.core.pf.js',
components: {},
namespace: config.namespace,
fsNamespace: config.fsNamespace
};

config.namespace = 'MyApp';
config.fsNamespace = 'my-app';
config.publicPath = '/my/custom/public/path/';
config.discoverPublicPath = false;

const res = await generateLoader(
config,
ctx,
appRegistry,
{}
);

const lines = res.split('\n');
expect(lines[1]).toEqual(`pretend i am code ("MyApp","my-app","/my/custom/public/path/",false,"myapp.core.js","myapp.core.pf.js","hydrated",[]) yeah me too`);
});

it('includes the injected app', async () => {
mockGetClientCoreFile.mockReturnValue(Promise.resolve(`pretend i am code ('__APP__') yeah me too`));
const res = await callGenerateLoader();
const lines = res.split('\n');
expect(lines[1]).toEqual(`pretend i am code ("MyApp","my-app","build/my-app/","myapp.core.js","myapp.core.pf.js","hydrated",[]) yeah me too`);
expect(lines[1]).toEqual(`pretend i am code ("MyApp","my-app","build/my-app/",true,"myapp.core.js","myapp.core.pf.js","hydrated",[]) yeah me too`);
});

});

async function callGenerateLoader(params?: {
Expand Down
38 changes: 10 additions & 28 deletions src/compiler/config/test/validate-paths.spec.ts
Expand Up @@ -6,23 +6,17 @@ import * as path from 'path';

describe('validatePaths', () => {

it('should set publicPath from custom buildDir', () => {
config.wwwDir = 'some-www';
config.buildDir = 'some-build';
validateBuildConfig(config);
expect(config.publicPath).toBe('/some-build/');
expect(path.isAbsolute(config.publicPath)).toBe(true);
});

it('should set publicPath and not force absolute path, but suffix with /', () => {
config.publicPath = 'my-crazy-public-path';
validateBuildConfig(config);
expect(config.publicPath).toBe('my-crazy-public-path/');
});
let config: Config;
const logger = mockLogger();
const sys = mockStencilSystem();

it('should set default publicPath and convert to absolute path', () => {
validateBuildConfig(config);
expect(config.publicPath).toBe('/build/');
beforeEach(() => {
config = {
sys: sys,
logger: logger,
rootDir: '/User/some/path/',
suppressTypeScriptErrors: true
};
});

it('should set default wwwIndexHtml and convert to absolute path', () => {
Expand Down Expand Up @@ -169,16 +163,4 @@ describe('validatePaths', () => {
expect(path.isAbsolute(config.globalStyle[0])).toBe(true);
});

var config: Config;
const logger = mockLogger();
const sys = mockStencilSystem();

beforeEach(() => {
config = {
sys: sys,
logger: logger,
rootDir: '/User/some/path/',
suppressTypeScriptErrors: true
};
});
});
50 changes: 50 additions & 0 deletions src/compiler/config/test/validate-public-path.spec.ts
@@ -0,0 +1,50 @@
import { Config } from '../../../declarations';
import { mockLogger, mockStencilSystem } from '../../../testing/mocks';
import { validateBuildConfig } from '../validate-config';
import * as path from 'path';


describe('validatePublicPath', () => {

let config: Config;
const logger = mockLogger();
const sys = mockStencilSystem();

beforeEach(() => {
config = {
sys: sys,
logger: logger,
rootDir: '/User/some/path/',
suppressTypeScriptErrors: true
};
});


it('should set publicPath from custom buildDir', () => {
config.wwwDir = 'some-www';
config.buildDir = 'some-build';
validateBuildConfig(config);
expect(config.publicPath).toBe('/some-build/');
expect(path.isAbsolute(config.publicPath)).toBe(true);
});

it('should set publicPath and not force absolute path, but suffix with /', () => {
config.publicPath = 'my-crazy-public-path';
validateBuildConfig(config);
expect(config.publicPath).toBe('my-crazy-public-path/');
});

it('should set discoverPublicPath to false if custom publicPath', () => {
config.publicPath = '/my-crazy-public-path/app/';
validateBuildConfig(config);
expect(config.publicPath).toBe('/my-crazy-public-path/app/');
expect(config.discoverPublicPath).toBe(false);
});

it('should set default publicPath, set discoverPublicPath true, and convert to absolute path', () => {
validateBuildConfig(config);
expect(config.publicPath).toBe('/build/');
expect(config.discoverPublicPath).toBe(true);
});

});

0 comments on commit 19095e7

Please sign in to comment.