fix: 换成基于 protractor 的 prerender 方式
This commit is contained in:
parent
65c7a3ed99
commit
bd6bc39c05
@ -9,6 +9,7 @@ addons:
|
|||||||
- ubuntu-toolchain-r-test
|
- ubuntu-toolchain-r-test
|
||||||
packages:
|
packages:
|
||||||
- g++-4.8
|
- g++-4.8
|
||||||
|
chrome: stable
|
||||||
branches:
|
branches:
|
||||||
except:
|
except:
|
||||||
- g3
|
- g3
|
||||||
@ -21,5 +22,6 @@ cache:
|
|||||||
before_install:
|
before_install:
|
||||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.10.1
|
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.10.1
|
||||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
- export PATH="$HOME/.yarn/bin:$PATH"
|
||||||
|
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
|
||||||
script:
|
script:
|
||||||
- "./aio/deploy-cn.sh"
|
- "./aio/deploy-cn.sh"
|
||||||
|
@ -9,7 +9,14 @@ commitMessage=$(git log --oneline -n 1)
|
|||||||
cd `dirname $0`
|
cd `dirname $0`
|
||||||
|
|
||||||
yarn build
|
yarn build
|
||||||
ts-node ./tools/translator/bin/ssr.ts
|
|
||||||
|
yarn start &
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
yarn update-webdriver
|
||||||
|
|
||||||
|
yarn prerender
|
||||||
|
|
||||||
if [[ ! -d "./ng-docs.github.io" ]]
|
if [[ ! -d "./ng-docs.github.io" ]]
|
||||||
then
|
then
|
||||||
|
@ -68,7 +68,8 @@
|
|||||||
"post~~build": "yarn build-404-page",
|
"post~~build": "yarn build-404-page",
|
||||||
"~~build-ie-polyfills": "webpack-cli src/ie-polyfills.js -o src/generated/ie-polyfills.min.js --mode production",
|
"~~build-ie-polyfills": "webpack-cli src/ie-polyfills.js -o src/generated/ie-polyfills.min.js --mode production",
|
||||||
"~~http-server": "http-server",
|
"~~http-server": "http-server",
|
||||||
"~~minify-lunr": "uglifyjs node_modules/lunr/lunr.js -c -m -o src/generated/lunr.min.js --source-map"
|
"~~minify-lunr": "uglifyjs node_modules/lunr/lunr.js -c -m -o src/generated/lunr.min.js --source-map",
|
||||||
|
"prerender": "protractor tests/e2e/protractor.conf.js --disableChecks --specs tests/e2e/prerender.e2e-spec.ts \"--grep=^Prerender \""
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.9.0 <11.0.0",
|
"node": ">=10.9.0 <11.0.0",
|
||||||
|
@ -132,7 +132,7 @@ export const svgIconProviders = [
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule.withServerTransition({appId: 'ng-docs'}),
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
CustomElementsModule,
|
CustomElementsModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
|
@ -8,5 +8,7 @@ if (environment.production) {
|
|||||||
enableProdMode();
|
enableProdMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||||
|
.catch(err => console.error(err));
|
||||||
|
});
|
||||||
|
88
aio/tests/e2e/prerender.e2e-spec.ts
Normal file
88
aio/tests/e2e/prerender.e2e-spec.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { browser, promise } from 'protractor';
|
||||||
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
|
import { concat, defer, Observable } from 'rxjs';
|
||||||
|
import { map, switchMap, tap } from 'rxjs/operators';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as mkdirp from 'mkdirp';
|
||||||
|
import { minify } from 'html-minifier';
|
||||||
|
import * as globby from 'globby';
|
||||||
|
|
||||||
|
const minifyOptions = {
|
||||||
|
collapseWhitespace: true,
|
||||||
|
ignoreCustomFragments: [/<code>[\s\S]*?<\/code>/],
|
||||||
|
minifyCSS: true,
|
||||||
|
minifyJS: true,
|
||||||
|
removeComments: true,
|
||||||
|
removeScriptTypeAttributes: true,
|
||||||
|
removeStyleLinkTypeAttributes: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
function fromPromise<T>(input: promise.Promise<T>): Observable<T> {
|
||||||
|
return Observable.create(observer => {
|
||||||
|
input.then((value) => {
|
||||||
|
observer.next(value);
|
||||||
|
observer.complete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function prerender(url: string): Observable<any> {
|
||||||
|
return defer(() => fromPromise(browser.get(url))).pipe(
|
||||||
|
tap(() => browser.executeScript('document.body.classList.add(\'no-animations\')')),
|
||||||
|
tap(() => browser.waitForAngular()),
|
||||||
|
switchMap(() => fromPromise(browser.executeScript('return document.documentElement.outerHTML'))),
|
||||||
|
map(content => minify(content, minifyOptions)),
|
||||||
|
map((content) => `<!DOCTYPE html>${content}`),
|
||||||
|
tap((html) => {
|
||||||
|
const filename = path.join('dist', `${url}.html`);
|
||||||
|
mkdirp.sync(path.dirname(filename));
|
||||||
|
writeFileSync(filename, html, 'utf-8');
|
||||||
|
}),
|
||||||
|
tap(() => console.log('Rendered ', url)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGuideUrls(): string[] {
|
||||||
|
const navigation = readFileSync('./content/navigation.json', 'utf-8');
|
||||||
|
return (navigation.match(/"url": "(.*?)"/g) || [])
|
||||||
|
.map((entry) => entry.replace(/^"url": "(.*?)".*$/, '$1'))
|
||||||
|
.filter(url => url.slice(0, 4) !== 'http');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApiUrls(): string[] {
|
||||||
|
return globby.sync('./src/generated/docs/api/**/*.json')
|
||||||
|
.map(file => file.replace(/^.*generated\/docs\/(.*).json$/, '$1'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function renderUrlsByTemplate(urls: string[], templateUrl: string): void {
|
||||||
|
const template = readFileSync(path.join('dist', `${templateUrl}.html`), 'utf-8');
|
||||||
|
urls.filter(url => url !== templateUrl).forEach((url) => {
|
||||||
|
console.log('Render by template: ', url);
|
||||||
|
const article = JSON.parse(readFileSync(`./src/generated/docs/${url}.json`, 'utf-8'));
|
||||||
|
const content = template
|
||||||
|
.replace(/<article>.*<\/article>/, article.contents)
|
||||||
|
.replace(/<title>.*<\/title>/, `${article.title} - Angular 官方文档`);
|
||||||
|
const filename = path.join('dist', `${url}.html`);
|
||||||
|
mkdirp.sync(path.dirname(filename));
|
||||||
|
writeFileSync(filename, content, 'utf-8');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Prerender', function () {
|
||||||
|
it('prerender', (done) => {
|
||||||
|
const apiUrls = getApiUrls();
|
||||||
|
const guideUrls = getGuideUrls().filter(url => url.match(/^(guide|tutorial|cli)/));
|
||||||
|
const otherUrls = getGuideUrls().filter(url => !url.match(/^(guide|tutorial|cli)/));
|
||||||
|
const urls = [guideUrls[0], apiUrls[0], otherUrls[0], 'index.html'];
|
||||||
|
const tasks = urls.map(url => prerender(url));
|
||||||
|
concat(...tasks).subscribe(undefined, undefined, () => {
|
||||||
|
console.log('Start render by template');
|
||||||
|
renderUrlsByTemplate(apiUrls, apiUrls[0]);
|
||||||
|
renderUrlsByTemplate(guideUrls, guideUrls[0]);
|
||||||
|
renderUrlsByTemplate(otherUrls, otherUrls[0]);
|
||||||
|
console.log('All rendered');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -3,8 +3,9 @@
|
|||||||
|
|
||||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||||
|
|
||||||
|
const MaxInt32 = Math.pow(2, 31) - 1;
|
||||||
exports.config = {
|
exports.config = {
|
||||||
allScriptsTimeout: 11000,
|
allScriptsTimeout: MaxInt32,
|
||||||
specs: [
|
specs: [
|
||||||
'./*.e2e-spec.ts'
|
'./*.e2e-spec.ts'
|
||||||
],
|
],
|
||||||
@ -12,7 +13,6 @@ exports.config = {
|
|||||||
browserName: 'chrome',
|
browserName: 'chrome',
|
||||||
// For Travis
|
// For Travis
|
||||||
chromeOptions: {
|
chromeOptions: {
|
||||||
binary: process.env.CHROME_BIN,
|
|
||||||
args: ['--no-sandbox']
|
args: ['--no-sandbox']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -21,7 +21,7 @@ exports.config = {
|
|||||||
framework: 'jasmine',
|
framework: 'jasmine',
|
||||||
jasmineNodeOpts: {
|
jasmineNodeOpts: {
|
||||||
showColors: true,
|
showColors: true,
|
||||||
defaultTimeoutInterval: 30000,
|
defaultTimeoutInterval: MaxInt32,
|
||||||
print: function() {}
|
print: function() {}
|
||||||
},
|
},
|
||||||
beforeLaunch: function() {
|
beforeLaunch: function() {
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
<aio-shell ng-version="6.0.0"
|
|
||||||
class="mode-stable sidenav-open page-guide-template-syntax folder-guide view-SideNav aio-notification-hide ">
|
|
||||||
<ul role="navigation"><!---->
|
|
||||||
<li class="ng-star-inserted"><a class="nav-link" href="/features" title="特性">特性</a></li>
|
|
||||||
<li class="ng-star-inserted"><a class="nav-link" href="/docs" title="文档">文档</a></li>
|
|
||||||
<li class="ng-star-inserted"><a class="nav-link" href="/resources" title="资源">资源</a></li>
|
|
||||||
<li class="ng-star-inserted"><a class="nav-link" href="/events" title="会议">会议</a></li>
|
|
||||||
<li class="ng-star-inserted"><a class="nav-link" href="https://blog.angular.io/" title="博客">博客</a></li>
|
|
||||||
<li class="ng-star-inserted"><a class="nav-link" href="/translations/cn/home" title="关于中文版">关于中文版</a></li>
|
|
||||||
<li class="ng-star-inserted"><a class="nav-link" href="https://ng-china.org" title="****2018 ngChina @ 杭州!****">****2018 ngChina @ 杭州!****</a></li>
|
|
||||||
</ul>
|
|
||||||
</aio-shell>
|
|
File diff suppressed because one or more lines are too long
@ -1,68 +0,0 @@
|
|||||||
import * as fs from 'fs';
|
|
||||||
import * as mkdirp from 'mkdirp';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as klawSync from 'klaw-sync';
|
|
||||||
import { minify } from 'html-minifier';
|
|
||||||
|
|
||||||
const rootElementPattern = /<aio-shell\b[\s\S]*<\/aio-shell>/;
|
|
||||||
|
|
||||||
const indexTemplate = fs.readFileSync('./dist/index.html', 'utf-8');
|
|
||||||
const aioShellTemplate = fs.readFileSync(__dirname + '/../assets/aio-shell-template.html');
|
|
||||||
const pageTemplate = indexTemplate.replace(rootElementPattern, `${aioShellTemplate}`);
|
|
||||||
|
|
||||||
const minifyOptions = {
|
|
||||||
collapseWhitespace: true,
|
|
||||||
ignoreCustomFragments: [/<code>[\s\S]*?<\/code>/],
|
|
||||||
minifyCSS: true,
|
|
||||||
minifyJS: true,
|
|
||||||
removeComments: true,
|
|
||||||
removeScriptTypeAttributes: true,
|
|
||||||
removeStyleLinkTypeAttributes: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
function composePage(url) {
|
|
||||||
console.log(`pre-rendering ${url}...`);
|
|
||||||
const { title, contents } = JSON.parse(fs.readFileSync(`./dist/generated/docs/${url}.json`, 'utf-8'));
|
|
||||||
|
|
||||||
if (!contents) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ssrContent = contents.replace(/href="(?!http)(.{2,}?)"/gi, 'href="/$1"');
|
|
||||||
const pageContent = pageTemplate
|
|
||||||
.replace('<title>Angular Docs</title>', `<title>${title} - Angular 官方文档</title>`)
|
|
||||||
.replace('<section class="sidenav-content" role="content" id="docs"></section>',
|
|
||||||
`<section class="sidenav-content" role="content" id="docs">${ssrContent}</section>`);
|
|
||||||
mkdirp.sync(path.dirname(`./dist/${url}`));
|
|
||||||
fs.writeFileSync(`./dist/${url}.html`, minify(pageContent, minifyOptions), 'utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildGuidePages(): void {
|
|
||||||
const navigation = fs.readFileSync('./content/navigation.json', 'utf-8');
|
|
||||||
(navigation.match(/"url": "(.*?)"/g) || [])
|
|
||||||
.map((entry) => entry.replace(/^"url": "(.*?)".*$/, '$1'))
|
|
||||||
.filter(url => url.slice(0, 4) !== 'http')
|
|
||||||
.forEach(url => composePage(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildApiPages(): void {
|
|
||||||
const apiList = klawSync('./dist/generated/docs/api', { nodir: true })
|
|
||||||
.map(file => file.path.replace(/^.*generated\/docs(\/.*).json$/, '$1'));
|
|
||||||
apiList.forEach(url => composePage(url));
|
|
||||||
|
|
||||||
const links = apiList.map(url => `<a href="${url}">${url}</a>`).join('\n');
|
|
||||||
const apiListContent = fs.readFileSync('./dist/api.html', 'utf-8')
|
|
||||||
.replace(rootElementPattern, `<aio-shell><h3>API List</h3>${links}</aio-shell>`);
|
|
||||||
|
|
||||||
fs.writeFileSync(`./dist/api.html`, minify(apiListContent, minifyOptions), 'utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildIndexPage(): void {
|
|
||||||
const indexTemplate = fs.readFileSync('./dist/index.html', 'utf-8');
|
|
||||||
const aioShellIndex = fs.readFileSync(__dirname + '/../assets/aio-shell-index.html');
|
|
||||||
const content = indexTemplate.replace(rootElementPattern, `${aioShellIndex}`);
|
|
||||||
fs.writeFileSync(`./dist/index.html`, minify(content, minifyOptions), 'utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
buildGuidePages();
|
|
||||||
buildApiPages();
|
|
||||||
buildIndexPage();
|
|
28
aio/tools/translator/tsconfig.json
Normal file
28
aio/tools/translator/tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compileOnSave": true,
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"outDir": "dist",
|
||||||
|
"baseUrl": "src",
|
||||||
|
"module": "commonjs",
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"target": "es5",
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es2017",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user