Repository: guess-js/guess Branch: master Commit: 0def89f41d43 Files: 452 Total size: 630.1 KB Directory structure: gitextract_rzzsvapf/ ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── DEVELOPING.md ├── LICENSE ├── README.md ├── experiments/ │ └── guess-static-sites/ │ ├── .gitignore │ ├── README.md │ ├── config.js │ ├── generatePredictions.js │ ├── package.json │ ├── predictiveFetching.js │ ├── server.js │ ├── src/ │ │ ├── models/ │ │ │ ├── pageView.js │ │ │ └── prediction.js │ │ ├── parser.js │ │ └── queryParams.js │ └── test/ │ ├── clientTests.js │ ├── fixtures/ │ │ ├── gaResponse.json │ │ ├── server.js │ │ └── test.html │ ├── gaClientTests.js │ └── serverTests.js ├── infra/ │ ├── e2e.ts │ ├── install.ts │ ├── pretest.ts │ └── test.ts ├── jest-puppeteer.config.js ├── jest.config.js ├── lerna.json ├── package.json ├── packages/ │ ├── common/ │ │ ├── interfaces.ts │ │ └── logger.ts │ ├── guess-ga/ │ │ ├── .npmignore │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── client.ts │ │ │ ├── ga.ts │ │ │ └── normalize.ts │ │ ├── test/ │ │ │ └── normalize.spec.ts │ │ ├── tsconfig.json │ │ └── webpack.config.js │ ├── guess-parser/ │ │ ├── .npmignore │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── angular/ │ │ │ │ ├── index.ts │ │ │ │ ├── modules.ts │ │ │ │ ├── route-parser.ts │ │ │ │ └── routes.ts │ │ │ ├── detector/ │ │ │ │ ├── detect.ts │ │ │ │ └── index.ts │ │ │ ├── language-service.ts │ │ │ ├── parser.ts │ │ │ ├── preact/ │ │ │ │ └── index.ts │ │ │ ├── react/ │ │ │ │ ├── base.ts │ │ │ │ ├── index.ts │ │ │ │ ├── react-jsx.ts │ │ │ │ └── react-tsx.ts │ │ │ └── utils.ts │ │ ├── test/ │ │ │ ├── angular.spec.ts │ │ │ ├── detect.spec.ts │ │ │ ├── fixtures/ │ │ │ │ ├── angular/ │ │ │ │ │ ├── .angular-cli.json │ │ │ │ │ ├── library/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── library.module.ts │ │ │ │ │ │ ├── nested/ │ │ │ │ │ │ │ └── library.module.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── app/ │ │ │ │ │ │ │ ├── about/ │ │ │ │ │ │ │ │ ├── about-routing.module.ts │ │ │ │ │ │ │ │ └── about.module.ts │ │ │ │ │ │ │ ├── app-routing.module.ts │ │ │ │ │ │ │ ├── app.component.css │ │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ │ ├── app.component.spec.ts │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ ├── bar.module.ts │ │ │ │ │ │ │ │ └── baz/ │ │ │ │ │ │ │ │ ├── baz.module.ts │ │ │ │ │ │ │ │ └── cycle-parent.ts │ │ │ │ │ │ │ ├── bar-simple.component.ts │ │ │ │ │ │ │ ├── foo/ │ │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ │ ├── baz-routing.module.ts │ │ │ │ │ │ │ │ │ ├── baz.component.ts │ │ │ │ │ │ │ │ │ └── baz.module.ts │ │ │ │ │ │ │ │ ├── foo-routing.module.ts │ │ │ │ │ │ │ │ ├── foo.component.ts │ │ │ │ │ │ │ │ └── foo.module.ts │ │ │ │ │ │ │ ├── lazy/ │ │ │ │ │ │ │ │ ├── lazy-routing.module.ts │ │ │ │ │ │ │ │ ├── lazy.component.ts │ │ │ │ │ │ │ │ └── lazy.module.ts │ │ │ │ │ │ │ ├── qux/ │ │ │ │ │ │ │ │ └── qux.module.ts │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── wrapper.module.ts │ │ │ │ │ │ ├── environments/ │ │ │ │ │ │ │ ├── environment.prod.ts │ │ │ │ │ │ │ └── environment.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── main.ts │ │ │ │ │ │ ├── polyfills.ts │ │ │ │ │ │ ├── styles.css │ │ │ │ │ │ ├── test.ts │ │ │ │ │ │ ├── tsconfig.app.json │ │ │ │ │ │ └── tsconfig.spec.json │ │ │ │ │ └── tsconfig.json │ │ │ │ ├── gatsby/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── .prettierrc │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── gatsby-browser.js │ │ │ │ │ ├── gatsby-config.js │ │ │ │ │ ├── gatsby-node.js │ │ │ │ │ ├── gatsby-ssr.js │ │ │ │ │ ├── package.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── header.js │ │ │ │ │ ├── layouts/ │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ └── index.js │ │ │ │ │ └── pages/ │ │ │ │ │ ├── 404.js │ │ │ │ │ ├── index.js │ │ │ │ │ └── page-2.js │ │ │ │ ├── ng8/ │ │ │ │ │ ├── .editorconfig │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── README.md │ │ │ │ │ ├── angular.json │ │ │ │ │ ├── browserslist │ │ │ │ │ ├── e2e/ │ │ │ │ │ │ ├── protractor.conf.js │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── app.e2e-spec.ts │ │ │ │ │ │ │ └── app.po.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ ├── karma.conf.js │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── app/ │ │ │ │ │ │ │ ├── app-routing.module.ts │ │ │ │ │ │ │ ├── app.component.css │ │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ │ ├── app.component.spec.ts │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ └── app.module.ts │ │ │ │ │ │ ├── assets/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ ├── environments/ │ │ │ │ │ │ │ ├── environment.prod.ts │ │ │ │ │ │ │ └── environment.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── main.ts │ │ │ │ │ │ ├── polyfills.ts │ │ │ │ │ │ ├── styles.css │ │ │ │ │ │ └── test.ts │ │ │ │ │ ├── tsconfig.app.json │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ ├── tsconfig.spec.json │ │ │ │ │ └── tslint.json │ │ │ │ ├── nx/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── angular.json │ │ │ │ │ ├── apps/ │ │ │ │ │ │ ├── customers-ui-e2e/ │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── integration/ │ │ │ │ │ │ │ └── customers.component.spec.ts │ │ │ │ │ │ ├── feat-home-e2e/ │ │ │ │ │ │ │ ├── cypress.json │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── fixtures/ │ │ │ │ │ │ │ │ │ └── example.json │ │ │ │ │ │ │ │ ├── integration/ │ │ │ │ │ │ │ │ │ └── home.component.spec.ts │ │ │ │ │ │ │ │ ├── plugins/ │ │ │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ │ │ └── support/ │ │ │ │ │ │ │ │ ├── commands.ts │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── tsconfig.e2e.json │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ └── tslint.json │ │ │ │ │ │ ├── ng-cli-app/ │ │ │ │ │ │ │ ├── browserslist │ │ │ │ │ │ │ ├── karma.conf.js │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── app/ │ │ │ │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ │ │ │ ├── app.component.scss │ │ │ │ │ │ │ │ │ ├── app.component.spec.ts │ │ │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ │ │ └── app.module.ts │ │ │ │ │ │ │ │ ├── assets/ │ │ │ │ │ │ │ │ │ ├── .gitkeep │ │ │ │ │ │ │ │ │ └── customers.json │ │ │ │ │ │ │ │ ├── environments/ │ │ │ │ │ │ │ │ │ ├── environment.prod.ts │ │ │ │ │ │ │ │ │ └── environment.ts │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── main.ts │ │ │ │ │ │ │ │ ├── polyfills.ts │ │ │ │ │ │ │ │ ├── styles.scss │ │ │ │ │ │ │ │ └── test.ts │ │ │ │ │ │ │ ├── tsconfig.app.json │ │ │ │ │ │ │ └── tsconfig.spec.json │ │ │ │ │ │ ├── ng-cli-app-e2e/ │ │ │ │ │ │ │ ├── cypress.json │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── fixtures/ │ │ │ │ │ │ │ │ │ └── example.json │ │ │ │ │ │ │ │ ├── integration/ │ │ │ │ │ │ │ │ │ ├── app.spec.ts │ │ │ │ │ │ │ │ │ ├── customers.spec.ts │ │ │ │ │ │ │ │ │ └── home.spec.ts │ │ │ │ │ │ │ │ ├── plugins/ │ │ │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ │ │ └── support/ │ │ │ │ │ │ │ │ ├── app.po.ts │ │ │ │ │ │ │ │ ├── commands.ts │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── tsconfig.e2e.json │ │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ │ └── shared-components-e2e/ │ │ │ │ │ │ ├── cypress.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── fixtures/ │ │ │ │ │ │ │ │ └── example.json │ │ │ │ │ │ │ ├── integration/ │ │ │ │ │ │ │ │ ├── info-box/ │ │ │ │ │ │ │ │ │ └── info-box.component.spec.ts │ │ │ │ │ │ │ │ └── navigation/ │ │ │ │ │ │ │ │ └── navigation.component.spec.ts │ │ │ │ │ │ │ ├── plugins/ │ │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ │ └── support/ │ │ │ │ │ │ │ ├── commands.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── tsconfig.e2e.json │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ └── tslint.json │ │ │ │ │ ├── docs/ │ │ │ │ │ │ ├── _config.yml │ │ │ │ │ │ └── index.md │ │ │ │ │ ├── index.js │ │ │ │ │ ├── jest.config.js │ │ │ │ │ ├── karma.conf.js │ │ │ │ │ ├── libs/ │ │ │ │ │ │ ├── .gitkeep │ │ │ │ │ │ ├── auth/ │ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ │ ├── jest.config.js │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ ├── lib/ │ │ │ │ │ │ │ │ │ ├── auth-routing.module.ts │ │ │ │ │ │ │ │ │ ├── auth.guard.spec.ts │ │ │ │ │ │ │ │ │ ├── auth.guard.ts │ │ │ │ │ │ │ │ │ ├── auth.module.spec.ts │ │ │ │ │ │ │ │ │ ├── auth.module.ts │ │ │ │ │ │ │ │ │ ├── auth.service.spec.ts │ │ │ │ │ │ │ │ │ ├── auth.service.ts │ │ │ │ │ │ │ │ │ └── login/ │ │ │ │ │ │ │ │ │ ├── login.component.html │ │ │ │ │ │ │ │ │ ├── login.component.scss │ │ │ │ │ │ │ │ │ ├── login.component.spec.ts │ │ │ │ │ │ │ │ │ └── login.component.ts │ │ │ │ │ │ │ │ └── test-setup.ts │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ ├── tsconfig.lib.json │ │ │ │ │ │ │ ├── tsconfig.spec.json │ │ │ │ │ │ │ └── tslint.json │ │ │ │ │ │ ├── customers/ │ │ │ │ │ │ │ ├── data/ │ │ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ │ │ ├── jest.config.js │ │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ │ ├── lib/ │ │ │ │ │ │ │ │ │ │ ├── customer.model.ts │ │ │ │ │ │ │ │ │ │ ├── customer.service.spec.ts │ │ │ │ │ │ │ │ │ │ └── customer.service.ts │ │ │ │ │ │ │ │ │ └── test-setup.ts │ │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ │ ├── tsconfig.lib.json │ │ │ │ │ │ │ │ ├── tsconfig.spec.json │ │ │ │ │ │ │ │ └── tslint.json │ │ │ │ │ │ │ └── ui/ │ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ │ ├── jest.config.js │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ ├── lib/ │ │ │ │ │ │ │ │ │ ├── customer-list/ │ │ │ │ │ │ │ │ │ │ ├── customer-list-datasource.ts │ │ │ │ │ │ │ │ │ │ ├── customer-list.component.html │ │ │ │ │ │ │ │ │ │ ├── customer-list.component.scss │ │ │ │ │ │ │ │ │ │ ├── customer-list.component.spec.ts │ │ │ │ │ │ │ │ │ │ └── customer-list.component.ts │ │ │ │ │ │ │ │ │ ├── customers-routing.module.ts │ │ │ │ │ │ │ │ │ ├── customers-ui.module.spec.ts │ │ │ │ │ │ │ │ │ ├── customers-ui.module.ts │ │ │ │ │ │ │ │ │ ├── customers.component.html │ │ │ │ │ │ │ │ │ ├── customers.component.scss │ │ │ │ │ │ │ │ │ ├── customers.component.spec.ts │ │ │ │ │ │ │ │ │ └── customers.component.ts │ │ │ │ │ │ │ │ └── test-setup.ts │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ ├── tsconfig.lib.json │ │ │ │ │ │ │ ├── tsconfig.spec.json │ │ │ │ │ │ │ └── tslint.json │ │ │ │ │ │ ├── home/ │ │ │ │ │ │ │ └── ui/ │ │ │ │ │ │ │ ├── .storybook/ │ │ │ │ │ │ │ │ ├── addons.js │ │ │ │ │ │ │ │ ├── config.js │ │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ │ └── webpack.config.js │ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ │ ├── jest.config.js │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ ├── lib/ │ │ │ │ │ │ │ │ │ ├── home-ui.module.spec.ts │ │ │ │ │ │ │ │ │ ├── home-ui.module.ts │ │ │ │ │ │ │ │ │ ├── home.component.html │ │ │ │ │ │ │ │ │ ├── home.component.scss │ │ │ │ │ │ │ │ │ ├── home.component.spec.ts │ │ │ │ │ │ │ │ │ ├── home.component.stories.ts │ │ │ │ │ │ │ │ │ └── home.component.ts │ │ │ │ │ │ │ │ └── test-setup.ts │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ ├── tsconfig.lib.json │ │ │ │ │ │ │ ├── tsconfig.spec.json │ │ │ │ │ │ │ └── tslint.json │ │ │ │ │ │ └── shared/ │ │ │ │ │ │ └── components/ │ │ │ │ │ │ ├── .storybook/ │ │ │ │ │ │ │ ├── addons.js │ │ │ │ │ │ │ ├── config.js │ │ │ │ │ │ │ ├── preview-head.html │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ └── webpack.config.js │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── jest.config.js │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── lib/ │ │ │ │ │ │ │ │ ├── info-box/ │ │ │ │ │ │ │ │ │ ├── info-box.component.html │ │ │ │ │ │ │ │ │ ├── info-box.component.scss │ │ │ │ │ │ │ │ │ ├── info-box.component.spec.ts │ │ │ │ │ │ │ │ │ ├── info-box.component.stories.ts │ │ │ │ │ │ │ │ │ └── info-box.component.ts │ │ │ │ │ │ │ │ ├── navigation/ │ │ │ │ │ │ │ │ │ ├── navigation.component.html │ │ │ │ │ │ │ │ │ ├── navigation.component.scss │ │ │ │ │ │ │ │ │ ├── navigation.component.spec.ts │ │ │ │ │ │ │ │ │ ├── navigation.component.stories.ts │ │ │ │ │ │ │ │ │ └── navigation.component.ts │ │ │ │ │ │ │ │ ├── shared-components.module.spec.ts │ │ │ │ │ │ │ │ └── shared-components.module.ts │ │ │ │ │ │ │ └── test-setup.ts │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ ├── tsconfig.lib.json │ │ │ │ │ │ ├── tsconfig.spec.json │ │ │ │ │ │ └── tslint.json │ │ │ │ │ ├── nx.json │ │ │ │ │ ├── package.json │ │ │ │ │ ├── scully.config.js │ │ │ │ │ ├── tools/ │ │ │ │ │ │ ├── schematics/ │ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ │ └── tsconfig.tools.json │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── tslint.json │ │ │ │ ├── preact-app/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── README.md │ │ │ │ │ ├── package.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── .babelrc │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── app.js │ │ │ │ │ │ ├── header/ │ │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ │ └── style.css │ │ │ │ │ │ └── info.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── manifest.json │ │ │ │ │ ├── routes/ │ │ │ │ │ │ ├── about/ │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ ├── home/ │ │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ │ └── style.css │ │ │ │ │ │ └── profile/ │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ └── style.css │ │ │ │ │ ├── style/ │ │ │ │ │ │ └── index.css │ │ │ │ │ └── tests/ │ │ │ │ │ ├── __mocks__/ │ │ │ │ │ │ └── browserMocks.js │ │ │ │ │ └── header.test.js │ │ │ │ ├── react-app/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── README.md │ │ │ │ │ ├── package.json │ │ │ │ │ ├── public/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── manifest.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── App.css │ │ │ │ │ ├── App.jsx │ │ │ │ │ ├── LazyRoute.jsx │ │ │ │ │ ├── index.css │ │ │ │ │ ├── index.js │ │ │ │ │ ├── intro/ │ │ │ │ │ │ └── Intro.jsx │ │ │ │ │ └── main/ │ │ │ │ │ ├── Main.jsx │ │ │ │ │ ├── kid/ │ │ │ │ │ │ └── Kid.jsx │ │ │ │ │ └── parent/ │ │ │ │ │ └── Parent.jsx │ │ │ │ ├── react-app-ts/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── README.md │ │ │ │ │ ├── package.json │ │ │ │ │ ├── public/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.css │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ ├── LazyRoute.tsx │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── intro/ │ │ │ │ │ │ │ └── Intro.tsx │ │ │ │ │ │ ├── main/ │ │ │ │ │ │ │ ├── Main.tsx │ │ │ │ │ │ │ ├── kid/ │ │ │ │ │ │ │ │ └── Kid.tsx │ │ │ │ │ │ │ └── parent/ │ │ │ │ │ │ │ └── Parent.tsx │ │ │ │ │ │ └── registerServiceWorker.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ ├── tsconfig.test.json │ │ │ │ │ └── tslint.json │ │ │ │ └── unknown/ │ │ │ │ └── package.json │ │ │ ├── parser.spec.ts │ │ │ ├── preact-jsx.spec.ts │ │ │ ├── react-jsx.spec.ts │ │ │ └── react-tsx.spec.ts │ │ ├── tsconfig.json │ │ └── webpack.config.js │ └── guess-webpack/ │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── api/ │ │ └── index.ts │ ├── index.ts │ ├── package.json │ ├── src/ │ │ ├── aot/ │ │ │ ├── aot.tpl │ │ │ └── guess-aot.ts │ │ ├── asset-observer.ts │ │ ├── compress.ts │ │ ├── declarations.ts │ │ ├── ga-provider.ts │ │ ├── guess-webpack.ts │ │ ├── prefetch-aot-plugin.ts │ │ ├── prefetch-plugin.ts │ │ ├── runtime/ │ │ │ ├── guess.ts │ │ │ ├── runtime.tpl │ │ │ └── runtime.ts │ │ └── utils.ts │ ├── test/ │ │ ├── e2e/ │ │ │ ├── delegate.spec.ts │ │ │ ├── next.spec.ts │ │ │ └── prefetch.spec.ts │ │ ├── fixtures/ │ │ │ ├── angular/ │ │ │ │ ├── .editorconfig │ │ │ │ ├── .gitignore │ │ │ │ ├── README.md │ │ │ │ ├── angular.json │ │ │ │ ├── e2e/ │ │ │ │ │ ├── protractor.conf.js │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── app.e2e-spec.ts │ │ │ │ │ │ └── app.po.ts │ │ │ │ │ └── tsconfig.e2e.json │ │ │ │ ├── package.json │ │ │ │ ├── routes.json │ │ │ │ ├── src/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── app-routing.module.ts │ │ │ │ │ │ ├── app.component.css │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ ├── app.component.spec.ts │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ ├── bar.component.ts │ │ │ │ │ │ │ └── bar.module.ts │ │ │ │ │ │ ├── foo/ │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ ├── baz-routing.module.ts │ │ │ │ │ │ │ │ ├── baz.component.ts │ │ │ │ │ │ │ │ └── baz.module.ts │ │ │ │ │ │ │ ├── foo-routing.module.ts │ │ │ │ │ │ │ ├── foo.component.ts │ │ │ │ │ │ │ └── foo.module.ts │ │ │ │ │ │ └── qux/ │ │ │ │ │ │ ├── qux-routing.module.ts │ │ │ │ │ │ ├── qux.component.ts │ │ │ │ │ │ └── qux.module.ts │ │ │ │ │ ├── assets/ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── browserslist │ │ │ │ │ ├── environments/ │ │ │ │ │ │ ├── environment.prod.ts │ │ │ │ │ │ └── environment.ts │ │ │ │ │ ├── index.html │ │ │ │ │ ├── karma.conf.js │ │ │ │ │ ├── main.ts │ │ │ │ │ ├── polyfills.ts │ │ │ │ │ ├── styles.css │ │ │ │ │ ├── test.ts │ │ │ │ │ ├── tsconfig.app.json │ │ │ │ │ ├── tsconfig.spec.json │ │ │ │ │ └── tslint.json │ │ │ │ ├── tsconfig.json │ │ │ │ ├── tslint.json │ │ │ │ └── webpack.extra.js │ │ │ ├── delegate/ │ │ │ │ ├── index.html │ │ │ │ ├── index.js │ │ │ │ ├── package.json │ │ │ │ └── webpack.config.js │ │ │ ├── next/ │ │ │ │ ├── components/ │ │ │ │ │ └── layout.js │ │ │ │ ├── next.config.js │ │ │ │ ├── package.json │ │ │ │ └── pages/ │ │ │ │ ├── about.js │ │ │ │ ├── contact.js │ │ │ │ └── index.js │ │ │ └── prefetch/ │ │ │ ├── about.js │ │ │ ├── contact.js │ │ │ ├── home.js │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── webpack.config.js │ │ └── unit/ │ │ ├── compress.spec.ts │ │ ├── runtime.spec.ts │ │ └── utils.spec.ts │ ├── tsconfig-api.json │ ├── tsconfig.json │ └── webpack.config.js ├── renovate.json ├── tsconfig.json └── tslint.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store .idea .vscode build node_modules npm-debug.log temp tmp-* .esm-cache/ dist lerna-debug.log *.d.ts ================================================ FILE: .travis.yml ================================================ language: node_js node_js: stable os: linux sudo: required install: - npm run bootstrap script: - npm run build - npm run test:ci - npm run e2e git: depth: 5 cache: directories: - node_modules ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Guess.js Please contribute to this repository if any of the following is true: - You are interested in improving the Guess.js source, docs or one of our related projects - You have expertise in community development, communication, or education - You want open source communities to be more collaborative and inclusive - You want to help lower the burden to first time contributors # How to contribute to our source code Prerequisites: - familiarity with [GitHub PRs](https://help.github.com/articles/using-pull-requests) (pull requests) and issues - knowledge of JavaScript and some [TypeScript](https://www.typescriptlang.org/) ## Submitting a Pull Request All submissions, including submissions by project members, require review. We use Github pull requests for this purpose. See our [developer guide](DEVELOPING.md) for instructions on building the project. Pull requests (fixes, new features, tests) are a great way to contribute to the project and help us make it better. Ideally, try to keep your PRs as focused as possible and keep your commits atomic and readable. To avoid disappointment when working on a PR, please ask us first in case someone else is already working on a PR for a change you wished to make. It's always a good idea to file an issue before starting work on a PR unless it's for something minor (such as a typo fix). We greatly appreciate any attention to tests. These help us validate that new work continues to function as expected over time. # How to contribute to our README or docs Prerequisites: - familiarity with [GitHub PRs](https://help.github.com/articles/using-pull-requests) (pull requests) and issues - knowledge of Markdown for editing `.md` documents In particular, this community seeks the following types of contributions: - ideas: participate in an Issues thread or start your own to have your voice heard - resources: submit a PR to add to our README with links to related content - outline sections: help us ensure that this repository is comprehensive. If there is a topic that is overlooked, please add it, even if it is just a stub in the form of a header and single sentence. Initially, most things fall into this category - write: contribute your expertise in an area by helping us expand the included content - copy editing: fix typos, clarify language, and generally improve the quality of the content - formatting: help keep content easy to read with consistent formatting - code: Fix issues or contribute new features to this or any related projects This contribution guide was inspired by others including contributing to Google open-source, React, Gulp and Babel. ================================================ FILE: DEVELOPING.md ================================================ # Developer's Guide This document explains how to build, test, and publish the packages from the monorepo. ## Installation In order to install all the dependencies run: ```bash npm run bootstrap ``` This will download all development dependencies for the monorepo and download the dependencies for each individual package. It will also call `lerna bootstrap` which will create symlinks for the cross-package dependencies. ## Build In order to build the packages run: ```bash npm run build ``` The command will build all the packages, topologically sorted. ## Publish To publish the packages, run: ```bash npm run publish ``` The `publish` script will delegate the execution to `lerna publish` which will take care of updating the dependencies' versions and publishing them to npm. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Minko Gechev and the Guess contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![Build Status](https://travis-ci.com/guess-js/guess.svg?branch=master)](https://travis-ci.com/guess-js/guess) # Guess.js (alpha) Libraries and tools for enabling data-driven user-experiences on the web. ## Quickstart ***For Webpack users:*** ### :black_circle: Data-driven bundling Install and configure [GuessPlugin](https://github.com/guess-js/guess/tree/master/packages/guess-webpack) - the Guess.js webpack plugin which automates as much of the setup process for you as possible. Should you wish to try out the modules we offer individually, the `packages` directory contains three packages: * [`ga`](https://github.com/guess-js/guess/tree/master/packages/guess-ga) - a module for fetching structured data from the Google Analytics API to learn about user navigation patterns. * [`parser`](https://github.com/guess-js/guess/tree/master/packages/guess-parser) - a module providing JavaScript framework parsing. This powers the route-parsing capabilities implemented in the Guess webpack plugin. * [`webpack`](https://github.com/guess-js/guess/tree/master/packages/guess-webpack) - a webpack plugin for setting up predictive fetching in your application. It consumes the `ga` and `parser` modules and offers a large number of options for configuring how predictive fetching should work in your application. ***For non-Webpack users:*** ### :black_circle: Data-driven loading Our [predictive-fetching for sites](https://github.com/guess-js/guess/tree/master/experiments/guess-static-sites) workflow provides a set of steps you can follow to integrate predictive fetching using the Google Analytics API to your site. This repo uses [Google Analytics](http://analytics.google.com) data to determine which page a user is mostly likely to visit next from a given page. A client-side script (which you'll add to your application) sends a request to the server to get the URL of the page it should fetch, then prefetches this resource. ## Learn More ### What is Guess.js? Guess.js provides libraries & tools to simplify predictive data-analytics driven approaches to improving user-experiences on the web. This data can be driven from any number of sources, including analytics or [machine learning](https://en.wikipedia.org/wiki/Machine_learning?sa=D&ust=1522637949792000) models. Guess.js aims to lower the friction of consuming and applying this thinking to all modern sites and apps, including building libraries & tools for popular workflows. Applying predictive data-analytics thinking to sites could be applied in a number of contexts: * **Predict the next page (or pages) a user is likely to visit and prefetch these pages, improving perceived page load performance and user happiness.** * Page-level: Prerender/Prefetch the page which is most likely to be visited next * Bundle-level: Prefetch the bundles associated with the top _N_ pages. On each page navigation, at all the neighbors of the current page, sorted in descending order by the probability to be visited. Fetch assets (JavaScript chunks) for the top N pages, depending on the current connection effective type. * **Predict the next piece of content (article, product, video) a user is likely to want to view and adjust or filter the user experience to account for this.** * **Predict the types of widgets an individual user is likely to interact with more (e.g games) and use this data to tailor a more custom experience.** By collaborating across different touch-points in the ecosystem where data-driven approaches could be easily applied, we hope to generalize common pieces of infrastructure to maximize their applicability in different tech stacks. ### Problems we're looking to solve * Developers using [``](https://developer.mozilla.org/en-US/docs/Web/HTTP/Link_prefetching_FAQ?sa=D&ust=1522637949794000) for future navigations heavily rely on manually reading descriptive analytics to inform their decisions for what to prefetch. * These decisions are often made at a point in time and.. * (1) are often not revisited as data trends change * (2) are very limited in how they are used. Implementations will often only prefetch content from a homepage or very small set of hero pages, but otherwise not do this for all of the possible entry points on a site. This can leave performance opportunities on the table. * (3) Require some amount of confidence about the data being used to drive decisions around using prefetching means that developers may not be adopting it out of worry they will waste bandwidth. `` is [currently used on 5%](https://www.chromestatus.com/metrics/feature/timeline/popularity/917) of total Chrome pageloads, but this could be higher. * Implementing predictive analytics is too complex for the average web developer. * Most developers are unfamiliar with how to leverage the [Google Analytics API](https://developers.google.com/analytics/devguides/reporting/core/v4/?sa=D&ust=1522637949796000) to determine the probability a page will be visited next. We lack: * (1) Page-level solution: a drop-in client-side solution for prefetching pages a user will likely visit * (2) Bundling-level solution: a set of plugins/tools that work with today’s JavaScript bundlers (e.g webpack) to cluster and generate the bundles/chunks a particular set of navigation paths could load quicker were they to be prefetched ahead of time. * Most developers are not yet familiar with how [Machine Learning](https://en.wikipedia.org/wiki/Machine_learning?sa=D&ust=1522637949797000) works. They are generally: * (1) Unsure how (and why) ML could be integrated into their existing (web) tech stacks * (2) What the value proposition of [TensorFlow](https://www.tensorflow.org/?sa=D&ust=1522637949797000) is or where solutions like the [CloudML](https://cloud.google.com/ml-engine/?sa=D&ust=1522637949798000) engine fit in. We have an opportunity to simplify the overhead associated with leveraging some of these solutions. * Best-in-class / low-friction approaches in this space are still slowly emerging and are not yet as accessible to web developers without ML or data-science backgrounds. * [Machine Learning meets Cloud: Intelligent Prefetching](https://iihnordic.com/blog/machine-learning-meets-the-cloud-intelligent-prefetching/?sa=D&ust=1522637949798000) by IIH Nordic * Tag Managers like [Google Tag Manager](https://www.google.com/analytics/tag-manager/?sa=D&ust=1522637949799000) can be used to decouple page content from the code tracking how the content is used. This allows web analysts to upgrade the tracking code in real-time with no site downtime. Tag managers allow a general solution for code injection and can be used to deploy intelligent prefetching. The advantages: analytics used to build the model comes from the tag manager. We can also send data live to the predictor without additional tracker overhead. After adding a few (of IIH Nordic’s) tags to a GTM install, a site can start to prefetch resources of the next pages and track load time saving opportunities. * IIH Nordic moved the predictive prefetching model to a web service the browser queries when a user visits a new page. The service responds to each request and takes advantage of Google Cloud, App Engine and [Cloud ML](https://cloud.google.com/ml-engine/?sa=D&ust=1522637949799000). Their solution chooses the most accurate model, choices include a [Markov model](https://en.wikipedia.org/wiki/Markov_model?sa=D&ust=1522637949800000) or most often a deep neural net in [TensorFlow](https://www.tensorflow.org/?sa=D&ust=1522637949800000). * With user behavior changing over time, predictive models require updating (training) from time to time. Training a model involves collecting and transforming data and fitting the parameters of the model accordingly. IIH Nordic use Google Cloud to pull data from a customer’s analytics service into a private data bucket in [BigQuery](https://cloud.google.com/bigquery/?sa=D&ust=1522637949800000). They process this data, train and test predictive models, updating the prediction service seamlessly. * IIH Nordic suggest small/slow sites update their models monthly. Larger sites may need to retrain daily or even hourly for news websites. * The benefit of training ML models in the cloud is ease of scale as additional machines, GPUs and processors can be added as needed. * [Machine Learning-Driven Bundling. The Future of JavaScript Tooling](http://blog.mgechev.com/2018/03/18/machine-learning-data-driven-bundling-webpack-javascript-markov-chain-angular-react/?sa=D&ust=1522637949801000) by Minko #### Initial priority: Improved Performance through Data-driven Prefetching The first large priority for Guess.js will be improving web performance through predictive prefetching of content. By building a model of pages a user is likely to visit, given an arbitrary entry-page, a solution could calculate the likelihood a user will visit a given next page or set of pages and prefetch resources for them while the user is still viewing their current page. This has the possibility of improving page-load performance for subsequent page visits as there's a strong chance a page will already be in the user's cache. ### Possible approaches to predictive fetching In order to predict the next page a user is likely to visit, solutions could use the [Google Analytics API](https://developers.google.com/analytics/devguides/reporting/core/v4/?sa=D&ust=1522637949828000). Google Analytics session data can be used to create a model to predict the most likely page a user is going to visit next on a site. The benefit of this session data is that it can evolve over time, so that if particular navigation paths change, the predictions can stay up to date too. With the availability of this data, an engine could insert `` tags to speed up the load time for the next page request. In some tests, such as Mark Edmondson's [Supercharging Page-Loads with R](http://code.markedmondson.me/predictClickOpenCPU/supercharge.html?sa=D&ust=1522637949828000), this led to a 30% improvement in page load times. The approach Mark used in his research involved using GTM tags and machine-learning to train a model for page predictions. This is an idea Mark continued in [Machine Learning meets the Cloud - Intelligent Prefetching](https://iihnordic.com/blog/machine-learning-meets-the-cloud-intelligent-prefetching/?sa=D&ust=1522637949828000). While this approach is sound, the methodology used could be deemed a little complex. Another approach that could be taken (which is simpler) is attempting to get accurate prediction data from the Google Analytics API. If you ran a report for the [Page](https://developers.google.com/analytics/devguides/reporting/core/dimsmets%23view%3Ddetail%26group%3Dpage_tracking%26jump%3Dga_pagepath?sa=D&ust=1522637949829000) and [Previous Page Path](https://developers.google.com/analytics/devguides/reporting/core/dimsmets%23view%3Ddetail%26group%3Dpage_tracking%26jump%3Dga_previouspagepath?sa=D&ust=1522637949829000) dimension combined with the [Pageviews](https://developers.google.com/analytics/devguides/reporting/core/dimsmets%23view%3Ddetail%26group%3Dpage_tracking%26jump%3Dga_pageviews?sa=D&ust=1522637949830000) and [Exits](https://developers.google.com/analytics/devguides/reporting/core/dimsmets%23view%3Ddetail%26group%3Dpage_tracking%26jump%3Dga_exits?sa=D&ust=1522637949830000) metrics this should provide enough data to wire up prefetches for most popular pages. #### Machine Learning for predictive fetching ML could help improve the overall accuracy of a solution's predictions, but is not a necessity for an initial implementation. Predictive fetching could be accomplished by training a model on the pages users are likely to visit and improving on this model over time. Deep neural networks are particularly good at teasing out the complexities that may lead to a user choosing one page over another, in particular, if we wanted to attempt a version of the solution that was catered to the pages an individual user might visit vs. the pages a "general/median" user might visit next. Fixed page sequences (prev, current, next) might be the easiest to begin dealing with initially. This means building a model that is unique to your set of documents. Model updates tend to be done periodically, so one might setup a nightly/weekly job to refresh based on new user behaviour. This could be done in real-time, but is likely complex, so doing it periodically might be sufficient. One could imagine a generic model representing behavioural patterns for users on a site that can either be driven by a trained status set, Google Analytics, or a custom description you plugin using a new layer into a router giving the site the ability to predictively fetch future pages, improving page load performance. ### Possible approaches to speculative prefetch #### Speculative prefetch on page load Speculative prefetch can prefetch pages likely be navigated to on page load. This assumes the existence of knowledge about the probability a page will need a certain next page or set of pages, or a training model that can provide a data-driven approach to determining such probabilities. Prefetching on page load can be accomplished in a number of ways, from deferring to the UA to decide when to prefetch resources (e.g at low priority with ``), during page idle time (via [requestIdleCallback()](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback?sa=D&ust=1522637949834000)()) or at some other interval. No further interaction is required by the user. #### Speculative prefetch when links come into the viewport A page could speculatively begin prefetching content when links in the page are visible in the viewport, signifying that the user may have a higher chance of wanting to click on them. This is an approach used by [Gatsby](https://www.gatsbyjs.org/?sa=D&ust=1522637949834000) (which uses [React](https://reactjs.org/?sa=D&ust=1522637949835000) and [React Router](https://github.com/ReactTraining/react-router?sa=D&ust=1522637949835000)). Their specific implementation is as follows: * In browsers that support [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API?sa=D&ust=1522637949836000), whenever a `` component becomes invisible, the link "votes" for the page linked to to be prefetched votes are worth slightly less points each time so links at the top of the page are prioritized over ones lower down * e.g. the top nav if a page is linked to multiple times, its vote count goes higher the prefetcher takes the top page and starts prefetching resources. * It's restricted to prefetching one page at a time so as to reduce contention over bandwidth with on page stuff (not a problem on fast networks. If a user visits a page and its resources haven't been fully downloaded, prefetching stops until the page is loaded to ensure the user waits as little time as possible. #### Speculative prefetch on user interaction A page could begin speculatively prefetching resources when a user indicates they are interested in some content. This can take many forms, including when a user chooses to hover over a link or some portion of UI that would navigate them to a separate page. The browser could begin fetching content for the link as soon as there was a clear indication of interest. This is an approach taken by JavaScript libraries such as [InstantClick](http://instantclick.io/?sa=D&ust=1522637949837000). ### Risks #### Data consumption As with any mechanism for prefetching content ahead of time, this needs to be approached very carefully. A user on a restricted data-plan may not appreciate or benefit as much from pages being fetched ahead of time, in particular if they start to eat up their data. There are mechanisms a site/solution could take to be mindful of this concern, such as respecting the [Save-Data](https://developers.google.com/web/updates/2016/02/save-data?sa=D&ust=1522637949832000) header. #### Prefetching undesirable pages Prefetching links to "logout" pages is likely undesirable. The same could be said of any pages that trigger an action on page-load (e.g one-click purchase). Solutions may wish to include a blacklist of URLs which are never prefetched to increase the likelihood of a prefetched page being useful. #### Web Standards ##### Future of rel=prerender Some of the attempts to accomplish similar proposals in the past have relied on ``. The Chrome team is currently exploring [deprecating rel=prerender](https://groups.google.com/a/chromium.org/forum/%23!topic/blink-dev/0nSxuuv9bBw?sa=D&ust=1522637949833000) in favor of [NoStatePrefetch](https://docs.google.com/document/d/16VCYGGWau483IMSxODpg5faZny1FJ6vNK2v-BuM5EhU/edit%23?sa=D&ust=1522637949833000) - a lighter version of this mechanism that only prefetches to the HTTP cache but uses no other state of the web platform. A solution should factor in whether it will be relying on the replacement to rel=prerender or using prefetch/preload/other approaches. There are two key differences between NoStatePrefetch and Prefetch: 1. nostate-prefetch is a mechanism, and `` is an API. The nostate-prefetch can be requested by other entry points: omnibox prediction, custom tabs, ``. 2. The implementation is different: `` prefetches one resource, but nostate-prefetch on top of that runs the preload scanner on the resource (in a fresh new renderer), discovers subresources and prefetches them as well (without recursing into preload scanner). ### Relevant Data Analytics There are [three primary types](https://halobi.com/blog/descriptive-predictive-and-prescriptive-analytics-explained/?sa=D&ust=1522637949802000) of data analytics worth being aware of in this problem space: descriptive, predictive and prescriptive. Each type is related and help teams leverage different kinds of insight. #### Descriptive - what has happened? Descriptive analytics summarizes raw data and turns it into something interpretable by humans. It can look at past events, regardless of when the events have occurred. Descriptive analytics allow teams to learn from past behaviors and this can help them influence future outcomes. Descriptive analytics could determine what pages on a site users have previously viewed and what navigation paths they have taken given any given entry page. #### Predictive - what will happen? Predictive analytics “predicts” what can happen next. Predictive analytics helps us understand the future and gives teams actionable insights using data. It provides estimates of the likelihood of a future outcome being useful. It’s important to keep in mind, few algorithms can predict future events with complete accuracy, but we can use as many signals that are available to us as possible to help improve baseline accuracy. The foundation of predictive analytics is based on probabilities we determine from data. Predictive analytics could predict the next page or set of pages a user is likely to visit given an arbitrary entry page. #### Prescriptive - what should we do? Prescriptive analytics enables prescribing different possible actions to guide towards a solution. Prescriptive analytics provides advice, attempting to quantify the impact future decisions may have to advise on possible outcomes before these decisions are made. Prescriptive analytics aims to not just predict what is going to happen but goes further; informing why it will happen and providing recommendations about actions that can take advantage of such predictions. Prescriptive analytics could predict the next page a user will visit, but also suggest actions such as informing you of ways you can customize their experience to take advantage of this knowledge. ### Relevant Prediction Models #### Markov Models The key objective of a prediction model in the prefetching problem space is to identify what the subsequent requests a user may need, given a specific page request. This allows a server or client to pre-fetch the next set of pages and attempt to ensure they are in a user’s cache before they directly navigate to the page. The idea is to reduce overall loading time. When this is implemented with care, this technique can reduce page access times and latency, improving the overall user experience. [Markov models](https://en.wikipedia.org/wiki/Markov_model?sa=D&ust=1522637949805000) have been widely used for researching and understanding stochastic (random probability distribution) process [[Ref](http://citeseerx.ist.psu.edu/viewdoc/download?doi%3D10.1.1.436.2396%26rep%3Drep1%26type%3Dpdf?sa=D&ust=1522637949806000), [Ref](https://www.researchgate.net/publication/266568034_Effective_Web_Cache_Pre-fetching_technique_using_Markov_Chain?sa=D&ust=1522637949806000)] . They have been demonstrated to be well-suited for modeling and predicting a user’s browsing behavior. The input for these problems tends to be the sequence of web pages accessed by a user or set of users (site-wide) with the goal of building Markov models we can use to model and predict the pages a user will most likely access next. A Markov process has states representing accessed pages and edges representing transition probabilities between states which are computed from a given sequence in an analytics log. A trained Markov model can be used to predict the next state given a set of k previous states. In some applications, first-order Markov models aren’t as accurate in predicting user browsing behaviors as these do not always look into the past to make a distinction between different patterns that have been observed. This is one reason higher-order models are often used. These higher-order models have limitations with state-space complexity, less broad coverage and sometimes reduced prediction accuracy. #### All-Kth-Order Markov Model One way [[Ref](http://www.siam.org/meetings/sdm01/pdf/sdm01_04.pdf?sa=D&ust=1522637949807000)] to overcome this problem is to train varying order Markov models, which we then use during the prediction phase. This was attempted in the [All-Kth-Order Markov model](http://www.siam.org/meetings/sdm01/pdf/sdm01_04.pdf?sa=D&ust=1522637949808000) proposed in this [Ref](https://dl.acm.org/citation.cfm?id%3D1251493?sa=D&ust=1522637949808000). This can make state-space complexity worse, however. Another approach is to identify frequent access patterns (longest repeating subsequences) and use this set of sequences for predictions. Although this approach can have an order of magnitude reduction on state-space complexity, it can reduce prediction accuracy. #### Selective Markov Models [Selective Markov models](http://www.siam.org/meetings/sdm01/pdf/sdm01_04.pdf?sa=D&ust=1522637949808000) (SMM) which only store some states within the model have also been proposed as a solution to state-space complexity tradeoffs. They begin with a All-Kth-Order Markov Model - a post-pruning approach is then used to prune states that are not expected to be accurate predictors. The result of this is a model which has the same prediction power of All-Kth-Order models with less space complexity and higher prediction accuracy. In [Deshpane and Karpis](http://www.siam.org/meetings/sdm01/pdf/sdm01_04.pdf?sa=D&ust=1522637949809000), different criteria to prune states in the model before prediction (frequency, confidence, error) are looked at. #### Semantic-pruned Selective Markov Models In [Mabroukeh and Ezeife](http://ieeexplore.ieee.org/document/5360449/?reload%3Dtrue?sa=D&ust=1522637949809000), the performance of semantic-rich 1st and 2nd order Markov models was studied and compared with that of higher-order SMM and semantic-pruned SMM. They discovered that semantic-pruned SMM have a 16% smaller size than frequency-pruned SMM and provide nearly an equal accuracy. #### Clustering Observing navigation patterns can allow us to analyze user behavior. This approach requires access to user-session identification, clustering sessions into similar clusters and developing a model for prediction using current and earlier access patterns. Much of the previous work in this field has relied on clustering schemes like the [K-means clustering](https://en.wikipedia.org/wiki/K-means_clustering?sa=D&ust=1522637949810000) technique with [Euclidean distance](https://en.wikipedia.org/wiki/Euclidean_distance?sa=D&ust=1522637949810000) for improving confidence of predictions. One of the drawbacks to using K-means is difficulty deciding on the number of clusters, selecting the initial random center and the order of page visits is not always considered. [Kumar et al](http://ieeexplore.ieee.org/document/7519368/?sa=D&ust=1522637949811000) investigated this, proposing a hierarchical clustering technique with a modified [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance?sa=D&ust=1522637949811000), pagerank using access time length, frequency and higher order Markov models for prediction. ### Research review Many of the papers referenced in the following section are centered around the Markov model, association rules and clustering. Papers highlighting relevant work related to pattern discovery for evolving page prediction accuracy are our focus. #### Sarukkai [2000] “[Link prediction and path analysis using Markov chains](https://www.sciencedirect.com/science/article/pii/S138912860000044X?sa=D&ust=1522637949813000)”. Uses first-order Markov models to model the sequence of web-pages requested by a user for predicting the next page they are likely to access. Markov chains allow the system to dynamically model URL access patterns observed in navigation logs based on previous state. A “personalized” Markov model is trained for each user and used to predict a user’s future sessions. In practice, it’s overly expensive to construct a unique model for each user and the cost of scaling this becomes more challenging when a site has a large user-base. #### Chun-Jung Lin [2005] ”[Using Hidden Markov Model to Predict the Surfing User’s Intention of Cyber Purchase on the Web](https://www.researchgate.net/publication/260319657_Using_Hidden_Markov_Model_to_Predict_the_Surfing_User's_Intention_of_Cyber_Purchase_on_the_Web?sa=D&ust=1522637949814000)” First paper to investigate Hidden Markov Models (HMM). Author collected web server logs, pruned the data and patched the paths users passed by. Based on HMM, author constructed a specific model for web browsing that predicts whether the users have the intention to purchase in real-time. Related measures, like speeding up the operation and their impact when in a purchasing mode are investigated. #### Elli Voudigari [2010-2011] ” [A Framework for Web Page Rank Prediction](https://link.springer.com/chapter/10.1007/978-3-642-23960-1_29?sa=D&ust=1522637949814000)”. Proposes a framework to predict ranking positions of a page based on their previous rankings. Assuming a set of successive Top-K rankings, the author identifies predictors based on different methodologies. Prediction quality is quantified as the similarity between predicted and actual rankings. Exhaustive experiments were performed on a real-world large scale dataset for both global and query-based top-K rankings. A variety of existing similarity measures for comparing Top-K ranked lists including a novel one captured in the paper. #### Mogul [1996] “ [Using predictive prefetching to improve World Wide Web latency](https://www.semanticscholar.org/paper/Using-predictive-prefetching-to-improve-World-Wide-Padmanabhan-Mogul/4d7e5e430bec3db4044b13ce8da7411f09c745f3?sa=D&ust=1522637949815000)”. Proposes using N-hop Markov models to predict the next web page users are likely to access. Pattern matches the user’s current access sequence with the user’s historical web access sequences to improve the prediction accuracy for prefetches. #### Borges, Levene [2007] “[Evaluating Variable-Length Markov Chain Models for Analysis of User Web Navigation Sessions](http://ieeexplore.ieee.org/document/4118703/?sa=D&ust=1522637949816000)”. Proposes dynamic clustering-based methods to increase Markov model accuracy in representing a collection of web navigation sessions. Uses a state cloning concept to duplicate states in a way separating in-links whose corresponding second-order probabilities diverge. The method proposed includes a clustering technique determining a way to assign in-links with similar second-order probabilities to the same clone. #### Banu Deniz Gunel [2010] ” [Investigating the Effect of Duration, Page Size and Frequency on Next Page Recommendation with Page Rank Algorithm](https://www.researchgate.net/publication/268366760_Investigating_the_Effect_of_Duration_Page_Size_and_Frequency_on_Next_Page_Recommendation_with_Page_Rank_Algorithm?sa=D&ust=1522637949817000)”. Extends the use of a page-rank algorithm with numerous navigational attributes: size of the page, duration time of the page, duration of transition (two page visits sequentially), frequency of page and transition. Defines a Duration Based Rank (DPR) and Popularity Based Page Rank (PPR). Author looked at the popularity of transitions and pages using duration information, using it with page size and visit frequency. Using the popularity value of pages, this paper attempts to improve conventional page rank algorithms and model a next page prediction under a given Top-N value. ## References * [Supercharging page-loads with R](http://code.markedmondson.me/predictClickOpenCPU/supercharge.html?sa=D&ust=1522637949840000) * [Using Google Analytics to predict clicks](https://www.noisetosignal.io/2016/11/using-google-analytics-to-predict-clicks-and-speed-up-your-website/?sa=D&ust=1522637949841000) * [Gatsby's Link](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-link?sa=D&ust=1522637949841000) * [Eve Dynamic Prerender](https://wordpress.org/plugins/eve-dynamic-prerender/?sa=D&ust=1522637949841000) * [InstartLogic - Multi-page Predictive Prefetching](https://www.instartlogic.com/blog/predicting-future-multi-page-predictive-prefetching?sa=D&ust=1522637949841000) * [Sirko Engine](https://github.com/sirko-io/engine?sa=D&ust=1522637949842000) - relies on Service Worker

Team


Minko Gechev

Addy Osmani

Katie Hempenius

Kyle Mathews
================================================ FILE: experiments/guess-static-sites/.gitignore ================================================ *.env *.pem *.p12 node_modules/ *.swp ================================================ FILE: experiments/guess-static-sites/README.md ================================================ # guess-static-sites Guess.js for non-Webpack sites. *Automatic, dynamic, intelligent prefetching for faster page loads.* :heavy_check_mark: **Automatic:** Once you've setup predictive fetching you'll automatically be using it on all your pages. (No more forgetting to take advantage of prefetch.) :heavy_check_mark: **Dynamic:** As your site changes, prefetch links will adjust accordingly. (No more hardcoded prefetch URLs.) :heavy_check_mark: **Intelligent:** Predictive fetching uses the client's connection type to determine whether a resource should be prefetched. ## How guess-static-sites works This directory uses Google Analytics data to determine which page a user is mostly likely to visit next from a given page (***generatePredictions.js***). A client-side script (which you'll add to your application) sends a request to the server you are running to get the URL of the page it should fetch, it then prefetches this resource (***script.js & server.js***). If a user is on a poor connection, prefetching will only occur if there's a high level of certainty that a user will go to a particular page next. If a client is using the Save-Data header, no prefetching will occur. ## Setup After downloading the Guess.js repo, cd to this directory and install the dependencies: ``` $ cd experiments/guess-static-sites $ npm install ``` ## A) Server setup Use this command to run the server: ``` $ node server.js ``` ## B) Script setup 1. Add predictiveFetching.js to your pages. 2. In predictiveFetching.js, replace ```'http://YOUR_SERVER_ENDPOINT/'``` with the server endpoint you'll be using. ## C) Database setup **Prerequisites:** - A Google Analytics account - Mongo installed on your computer/server [(Instructions)](https://docs.mongodb.com/manual/installation/) This is the final, and lengthiest, part of the setup process - but it should only take about 5-10 minutes to complete if you already have Mongo and a Google Analytics account. Afterwards you'll be ready to consume and analyze your Google Analytics data. ### Create Your Credentials #### i) Create a Service Account 1. Go to the [Credentials](https://console.developers.google.com/apis/credentials) page in the Google APIs console. 2. If you don't have an existing Google Cloud project, click "Create" to create a new project. Otherwise, use the dropdown in the upper left corner to select the existing project that you'd like to use. 3. Select "Service Account key" from the "Create credentials" dropdown. 4. Fill out the form for creating a service account key: - **Service account dropdown:** Select "New Service Account". - **Service account name:** Give your service account a name. - **Role:** Select "Service Account User" ("Service Accounts" > "Service Account User"). - **Service account ID:** This field will automatically be pre-filled, but you can change this if you would like. - **Key type:** Select P12 key. 5. Click Create. #### ii) Setup Your Private Key Your private key should have started downloading when you clicked the "Create" button for creating your service account. 1. Note the private key password. You'll be prompted for this password in Step 3. 2. Move this key into the directory for this project. 3. Generate a *.p12 certificate by running this command from the directory for this project: ``` $ openssl pkcs12 -in *.p12 -out key.pem -nodes -clcerts ``` ### Configure Google Analytics #### i) Add service account to Google Analytics The service account that you just created needs to be added as a user to your Google analytics account. 1. Login to your [Google Analytics](https://analytics.google.com/analytics/web/) account. 2. Add a new user. (Admin > User Management > + > Add New Users) - **Email Address** The email address of the service account you created. It should look something like this: example@example-project-123456.iam.gserviceaccount.com. - **Permissions:** Select "Read & Analyze." *Note: A service account can only be associated with one Google Analytics account at a time. Thus, if you want to use predictive fetching on multiple sites, you'll need to create a separate service account for each.* #### ii) Enable the Google Analytics Reporting API You can enable this [here.](https://console.developers.google.com/flows/enableapi?apiid=analyticsreporting.googleapis.com&credential=client_key) ### Create your .env file This file will hold your confidential configuration details. #### i) Create the file ``` $ touch .env ``` #### ii) Add your information Your file should look like this (replace with your own values): ```bash VIEW_ID=12345678 SERVICE_ACCOUNT_EMAIL=example@example-project-123456.iam.gserviceaccount.com ``` To find your view ID, go to [Google Analytics](https://analytics.google.com/analytics/web/). Click the accounts dropdown (it's located in the upper lefthand corner of the screen, right next to the Google Analytics logo). The dialog that opens will have three columns: Analytics Accounts, Properties & Apps, & Views. The far right column ("Views") will contain the View ID for your site. ### Generate predictions #### i) Start mongod If mongod is not running, start it: ``` $ mongod ``` #### ii) Run script ``` $ node generatePredictions.js ``` If this is successful, you should see something like this in the console for each page: ``` { pagePath: 'dogs/poodle.html', pageviews: 300, exits: 200, nextPageviews: 100, nextExits: 100, nextPages: [ { pagePath: '/dogs/puppies/', pageviews: 100 } ], percentExits: 0.6666666666666666, topNextPageProbability: 0.3333333333333333 } ``` You can also explore the results in Mongo: ``` $ mongo $ use guessjs_dev $ db.predictions.find() ``` #### iii) (Optional) Setup a cron job It is recommended that you set up a cron job to periodically re-run generatePredictions.js. This will ensure that the prefetch links are as accurate as possible. The ideal frequency of this cron job depends on how frequently your site changes. You can also experiment with the date range of data that is used to generate predictions. By default, GuessJS uses the last 30 days of traffic to generate predictions, but this value can be changed (this is located in file: *src/queryParams.js*). It's important to have a sufficiently large data set, so the ideal date range will largely depend on a site's traffic volume. A very high-traffic site might find that using the last 1-7 days of traffic works best, while a low-traffic site find that using the last 30 days works best. ================================================ FILE: experiments/guess-static-sites/config.js ================================================ const env = process.env.NODE_ENV require('dotenv').config() const dev = { auth: { keyFileName: 'key.pem', viewID: process.env.VIEW_ID, serviceAccountEmail: process.env.SERVICE_ACCOUNT_EMAIL }, db: { mongoURL: 'mongodb://localhost:27017/guessjs_dev' }, server: { port: 3000, url: 'http://localhost:3000' } } const test = { auth: { keyFileName: 'key.pem', viewID: process.env.VIEW_ID, serviceAccountEmail: process.env.SERVICE_ACCOUNT_EMAIL }, db: { mongoURL: 'mongodb://localhost:27017/guessjs_test' }, server: { port: 3000, url: 'http://localhost:3000' } } const prod = { auth: { keyFileName: 'key.pem', viewID: process.env.VIEW_ID, serviceAccountEmail: process.env.SERVICE_ACCOUNT_EMAIL }, db: { mongoURL: 'mongodb://localhost:27017/guessjs_prod' }, server: { port: 3000, url: 'http://localhost:3000' } } const config = { dev, test, prod } module.exports = config[env] || config['dev'] ================================================ FILE: experiments/guess-static-sites/generatePredictions.js ================================================ /* This scripts retrieves recent reporting data from Google Analytics and uses this to determine the "Most Likely Next Page" for each page on your site. This data is saved in Mongo. */ const {google} = require('googleapis') const fs = require('fs') const queryParams = require('./src/queryParams') const parser = require('./src/parser') const config = require('./config') const path = require('path') const authClient = new google.auth.JWT({ email: config.auth.serviceAccountEmail, key: fs.readFileSync(path.join(__dirname, config.auth.keyFileName), 'utf8'), scopes: ['https://www.googleapis.com/auth/analytics.readonly'] }) const getData = async (authClient) => { await authClient.authorize() const analytics = google.analyticsreporting({ version: 'v4', auth: authClient }) const response = await analytics.reports.batchGet(queryParams) await parser.saveReports(response.data.reports) process.exit() } getData(authClient) ================================================ FILE: experiments/guess-static-sites/package.json ================================================ { "name": "guess-static-sites", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "generate-predictions-tests": "NODE_ENV=test mocha ./test/gaClientTests.js", "client-tests": "NODE_ENV=test mocha ./test/clientTests.js", "server-tests": "NODE_ENV=test mocha ./test/serverTests.js", "build": "babel src/client.js --out-file dist/client.js --presets minify" }, "author": "", "license": "ISC", "dependencies": { "body-parser": "^1.18.2", "dotenv": "^8.0.0", "express": "^4.16.3", "fs": "0.0.2", "googleapis": "^67.0.0", "mongoose": "^5.0.14" }, "devDependencies": { "babel-cli": "6.26.0", "babel-minify": "0.5.1", "babel-preset-es2015": "6.24.1", "babel-preset-minify": "0.5.1", "chai": "4.3.0", "cors": "2.8.5", "http-server": "0.12.1", "mocha": "8.3.1", "path": "0.12.7", "puppeteer": "7.1.0", "standard": "16.0.3", "supertest": "6.1.3" } } ================================================ FILE: experiments/guess-static-sites/predictiveFetching.js ================================================ const ENDPOINT = 'http://YOUR_SERVER_ENDPOINT/' const setCookie = function (obj) { const cookieStr = obj.name + '=' + obj.value + '; max-age=' + obj.maxAge document.cookie = cookieStr } const appendLinkTo = function (url) { const linkTag = document.createElement('link') linkTag.rel = 'prefetch' linkTag.href = url document.head.appendChild(linkTag) } const getConnectionType = function () { return window.navigator.connection ? window.navigator.connection.effectiveType : '' } const getUserFlow = function () { const result = [] document.cookie.split(';').forEach(c => { const prefix = c.trim().substr(0, 3) if (prefix === 'pf_') { result.push(c.trim()) } }) return result } if (!document.hidden) { xhr = new XMLHttpRequest() xhr.open('POST', ENDPOINT) xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8') xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { const data = JSON.parse(xhr.responseText) if (data['prefetchPath'] !== '') { appendLinkTo(window.location.origin + data.prefetchPath) } setCookie({ name: 'pf_' + data['pageViewId'], value: Math.round(new Date() / 1000), maxAge: 300 }) } } const requestBody = { 'pagePath': window.location.pathname, 'userFlow': getUserFlow(), 'clientInfo': { 'connectionType': getConnectionType(), 'platform': window.navigator.platform, 'language': window.navigator.language } } xhr.send(JSON.stringify(requestBody)) } ================================================ FILE: experiments/guess-static-sites/server.js ================================================ const express = require('express') const bodyParser = require('body-parser') const cors = require('cors') const mongoose = require('mongoose') const Prediction = require('./src/models/prediction') const PageView = require('./src/models/pageView') const config = require('./config') const app = express() app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: true })) app.use(cors()) app.all('/', function (req, res, next) { res.header('Access-Control-Allow-Origin', '*') res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS') res.header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type') next() }) const certaintyThresholdsByConnectionType = { 'slow-2g': 0.95, '2g': 0.9, '3g': 0.5, '4g': 0.2 } const shouldPrefetch = function (request, prediction) { if (prediction === null) { return false } if (request.header('Save-Data')) { return false } const connectionType = request.body.clientInfo.connectionType const threshold = certaintyThresholdsByConnectionType[connectionType] if (threshold === undefined) { return false } else { return parseFloat(prediction.nextPageCertainty) > parseFloat(threshold) } } const getPreviousPageId = function (cookies) { let latestTimestamp = 0 let latestId cookies.forEach(c => { const timestamp = parseInt(c.split('=')[1]) if (timestamp > latestTimestamp) { latestTimestamp = timestamp latestId = c.substring(c.indexOf('_') + 1, c.indexOf('=')) } }) return latestId } app.post('/', async (req, res) => { mongoose.connect(config.db.mongoURL) const prediction = await Prediction.findOne({'pagePath': req.body['pagePath']}) const prefetchPath = shouldPrefetch(req, prediction) ? prediction['nextPagePath'] : '' const pageView = await PageView.create({ pagePath: req.body['pagePath'], clientInfo: req.body['clientInfo'], userFlow: req.body['userFlow'], prefetchPath: prefetchPath }) // Update data about previous page view if (req.body.userFlow.length > 0) { const id = getPreviousPageId(req.body.userFlow) await PageView.findByIdAndUpdate(id, {'actualNextPagePath': req.body.pagePath}) } res.json({ 'pageViewId': pageView._id, 'prefetchPath': prefetchPath }) }) app.listen(config.server.port) module.exports = app ================================================ FILE: experiments/guess-static-sites/src/models/pageView.js ================================================ const mongoose = require('mongoose') const pageViewSchema = mongoose.Schema({ pagePath: String, clientInfo: Object, userFlow: Array, prefetchPath: String, actualNextPagePath: String }) const PageView = mongoose.model('PageView', pageViewSchema) module.exports = PageView ================================================ FILE: experiments/guess-static-sites/src/models/prediction.js ================================================ const mongoose = require('mongoose') const predictionSchema = mongoose.Schema({ pagePath: String, nextPagePath: String, nextPageCertainty: Number }) const Prediction = mongoose.model('Prediction', predictionSchema) module.exports = Prediction ================================================ FILE: experiments/guess-static-sites/src/parser.js ================================================ const mongoose = require('mongoose') const Prediction = require('./models/prediction') const config = require('../config') // Generates & saves predictions based off Google Analytics response const saveReports = async (reports) => { let [report] = reports let {rows} = report.data const data = {} for (let row of rows) { let [previousPagePath, pagePath] = row.dimensions let pageviews = +row.metrics[0].values[0] let exits = +row.metrics[0].values[1] if (/\?.*$/.test(pagePath) || /\?.*$/.test(previousPagePath)) { pagePath = pagePath.replace(/\?.*$/, '') previousPagePath = previousPagePath.replace(/\?.*$/, '') } // Ignore pageviews where the current and previous pages are the same. if (previousPagePath == pagePath) continue if (previousPagePath != '(entrance)') { data[previousPagePath] = data[previousPagePath] || { pagePath: previousPagePath, pageviews: 0, exits: 0, nextPageviews: 0, nextExits: 0, nextPages: {} } data[previousPagePath].nextPageviews += pageviews data[previousPagePath].nextExits += exits if (data[previousPagePath].nextPages[pagePath]) { data[previousPagePath].nextPages[pagePath] += pageviews } else { data[previousPagePath].nextPages[pagePath] = pageviews } } data[pagePath] = data[pagePath] || { pagePath: pagePath, pageviews: 0, exits: 0, nextPageviews: 0, nextExits: 0, nextPages: {} } data[pagePath].pageviews += pageviews data[pagePath].exits += exits } // Converts each pages `nextPages` object into a sorted array. Object.keys(data).forEach((pagePath) => { const page = data[pagePath] page.nextPages = Object.keys(page.nextPages) .map((pagePath) => ({ pagePath, pageviews: page.nextPages[pagePath] })) .sort((a, b) => b.pageviews - a.pageviews) }) // Creates a sorted array of pages from the data object. const pages = Object.keys(data) .filter((pagePath) => data[pagePath].nextPageviews > 0) .map((pagePath) => { const page = data[pagePath] const {exits, nextPageviews, nextPages} = page page.percentExits = exits / (exits + nextPageviews) page.topNextPageProbability = nextPages[0].pageviews / (exits + nextPageviews) return page }) .sort((a, b) => { // return b.topNextPageProbability - a.topNextPageProbability return b.pageviews - a.pageviews }) for (let page of pages) { // TODO - remove console logs console.log(page) console.log('\n') } await savePagesToDatabase(pages) } // Adds each page (and its associated prediction) to the database. // If a page already exists, its record is updated based on the most recent Google Analytics data. const savePagesToDatabase = async (pages) => { mongoose.connect(config.db.mongoURL) for (let page of pages) { const prediction = { pagePath: page.pagePath, nextPagePath: page.nextPages[0] ? page.nextPages[0].pagePath : '', nextPageCertainty: page.nextPages[0] ? page.topNextPageProbability : '' } await Prediction.update({'pagePath': prediction.pagePath}, prediction, {'upsert': true}) } } module.exports = {saveReports: saveReports} ================================================ FILE: experiments/guess-static-sites/src/queryParams.js ================================================ const config = require('../config') const queryParams = { resource: { reportRequests: [ { viewId: config.auth.viewID, dateRanges: [{startDate: '30daysAgo', endDate: 'yesterday'}], metrics: [ {expression: 'ga:pageviews'}, {expression: 'ga:exits'} ], dimensions: [ {name: 'ga:previousPagePath'}, {name: 'ga:pagePath'} ], orderBys: [ {fieldName: 'ga:previousPagePath', sortOrder: 'ASCENDING'}, {fieldName: 'ga:pageviews', sortOrder: 'DESCENDING'} ], pageSize: 10000 } ] } } module.exports = queryParams ================================================ FILE: experiments/guess-static-sites/test/clientTests.js ================================================ // To run tests: $ npm run script-tests // Server responses are stubbed, but test server needs to be running // ($ NODE_ENV=test node ./fixtures/server.js) // Mongod should be running // Tests run against dist/client.js; to regenerate this: $ npm run build const puppeteer = require('puppeteer') const { expect } = require('chai') const globalVariables = {'browser': global['browser'], 'expect': global['expect']} const should = require('chai').should() // TODO - Use config const SERVER_URL = 'http://localhost:3000/' const PAGE_URL = 'http://localhost:8080/test.html' before(async () => { global.expect = expect global.browser = await puppeteer.launch({ headless: false, slowMo: 100, timeout: 10000 }) }) after(() => { browser.close() global.browser = globalVariables.browser global.expect = globalVariables.expect }) describe('Predictve fetching script ', function () { let page before(async function () { page = await browser.newPage() await page._client.send('Network.clearBrowserCookies') page.on('console', msg => console.log('PAGE LOG:', msg.text())) }) after(async function () { await page.close() }) describe('makes a request', async function () { it('should have the correct request body', async function () { let requests = [] page.on('request', interceptedRequest => { requests.push(interceptedRequest) }) const unrelatedCookie = { 'name': 'some_other_cookie', 'value': 'abc', 'domain': 'localhost', 'path': '/test.html', 'expires': (Date.now() / 1000) + 100 } const cookie1 = { 'name': 'pf_cookie1', 'value': '111', 'domain': 'localhost', 'path': '/test.html', 'expires': (Date.now() / 1000) + 100 } const cookie2 = { 'name': 'pf_cookie2', 'value': '222', 'domain': 'localhost', 'path': '/test.html', 'expires': (Date.now() / 1000) + 100 } await page.setCookie(unrelatedCookie) await page.setCookie(cookie1) await page.setCookie(cookie2) await page.goto(PAGE_URL) request = requests.find(req => { let URL = SERVER_URL return req.url() === URL && req.method() === 'POST' }) page.waitForNavigation({waitUntil: 'networkidle2'}) expect(request).to.exist const actualResponseBody = JSON.parse(request.postData()) expect(actualResponseBody['pagePath']).to.equal('/test.html') // not.be.empty (vs. a particular value like "4g" or "MacIntel") // is used so these tests will work cross-environment expect(actualResponseBody['clientInfo']['connectionType']).to.not.be.empty expect(actualResponseBody['clientInfo']['platform']).to.not.be.empty expect(actualResponseBody['clientInfo']['language']).to.not.be.empty expect(actualResponseBody['userFlow'].length).to.equal(2) expect(actualResponseBody['userFlow']).to.contain('pf_cookie1=111') expect(actualResponseBody['userFlow']).to.contain('pf_cookie2=222') }) }) describe('request callback', function () { const setupResponse = async (page, responseBody) => { await page.setRequestInterception(true) page.on('request', req => { if (req.url() === SERVER_URL && req.method() === 'POST') { req.respond({ status: 200, contentType: 'application/json', headers: {'Access-Control-Allow-Origin': '*'}, body: JSON.stringify(responseBody) }) } else { req.continue() } }) } before(async function () { page = await browser.newPage() setupResponse(page, {'pageViewId': '123', 'prefetchPath': ''}) await page._client.send('Network.clearBrowserCookies') await page.goto(PAGE_URL) await page.waitFor(100) }) describe("when there's no matching prefetch", function () { it('should not append a link', async function () { const link = await page.evaluate(() => { return document.querySelector('link') }) expect(link).to.be.null }) it('should set prefetch cookies', async function () { const cookies = await page.cookies() expect(cookies.length).to.equal(1) expect(cookies[0].name).to.equal('pf_123') const currentTime = new Date() / 1000 // Buffer user to account for slight timestamp difference // between when cookie was created and when this test runs const buffer = 5 const cookieValue = parseInt(cookies[0].value) expect(cookieValue).to.be.closeTo(currentTime, buffer) const cookieMaxAge = 300 expect(cookies[0].expires).to.be.closeTo(currentTime + cookieMaxAge, buffer) }) }) describe("when there's a matching prefetch", function () { before(async function () { page = await browser.newPage() setupResponse(page, {'pageViewId': '123', 'prefetchPath': '/nextpage.html'}) await page._client.send('Network.clearBrowserCookies') await page.goto(PAGE_URL) await page.waitFor('link') }) it('should append a link', async function () { const link = await page.evaluate(() => { return document.querySelector('link') }) expect(link).to.exist }) it('should use prefetch', async function () { const resourceHint = await page.evaluate(() => { return document.querySelector('link').rel }) expect(resourceHint).to.equal('prefetch') }) it('should load the correct resource', async function () { const href = await page.evaluate(() => { return document.querySelector('link').href }) // Regex matches that "/nextpage.html" apears at the end of the string expect(href).to.match(/\/nextpage\.html$/) }) it('should set prefetch cookies', async function () { const cookies = await page.cookies() expect(cookies[0].name).to.equal('pf_123') const currentTime = new Date() / 1000 // Buffer user to account for slight timestamp difference // between when cookie was created and when this test runs const buffer = 5 const cookieValue = parseInt(cookies[0].value) expect(cookieValue).to.be.closeTo(currentTime, buffer) const cookieMaxAge = 300 expect(cookies[0].expires).to.be.closeTo(currentTime + cookieMaxAge, buffer) }) }) }) }) ================================================ FILE: experiments/guess-static-sites/test/fixtures/gaResponse.json ================================================ {"testData": [{ "columnHeader": { "dimensions": ["ga:previousPagePath", "ga:pagePath"], "metricHeader": { "metricHeaderEntries": [{ "name": "ga:pageviews", "type": "INTEGER" }, { "name": "ga:exits", "type": "INTEGER" }] } }, "data": { "rows": [{ "dimensions": ["(entrance)", "/page/turtles/"], "metrics": [{ "values": ["13", "9"] }] }, { "dimensions": ["(entrance)", "/page/dogs/"], "metrics": [{ "values": ["6", "5"] }] }, { "dimensions": ["(entrance)", "/"], "metrics": [{ "values": ["1", "1"] }] }, { "dimensions": ["(entrance)", "/page/cats/"], "metrics": [{ "values": ["1", "1"] }] }, { "dimensions": ["/turtles/turtle1.html", "/page/turtles/"], "metrics": [{ "values": ["3", "3"] }] }, { "dimensions": ["/page/turtles/", "/turtles/turtle1.html"], "metrics": [{ "values": ["3", "0"] }] }, { "dimensions": ["/page/turtles/", "/turtles/tortise.html"], "metrics": [{ "values": ["1", "1"] }] }, { "dimensions": ["/page/dogs/", "/dogs/dog_photo2.html"], "metrics": [{ "values": ["1", "0"] }] }, { "dimensions": ["/dogs/dog_photo2.html", "/page/dogs/"], "metrics": [{ "values": ["1", "1"] }] }], "totals": [{ "values": ["30", "21"] }], "rowCount": 9, "minimums": [{ "values": ["0", "0"] }], "maximums": [{ "values": ["13", "9"] }] } }, { "columnHeader": { "dimensions": ["ga:previousPagePath", "ga:pagePath"], "metricHeader": { "metricHeaderEntries": [{ "name": "ga:pageviews", "type": "INTEGER" }, { "name": "ga:exits", "type": "INTEGER" }] } }, "data": { "rows": [{ "dimensions": ["(entrance)", "/page/turtles/"], "metrics": [{ "values": ["13", "9"] }] }, { "dimensions": ["(entrance)", "/page/dogs/"], "metrics": [{ "values": ["6", "5"] }] }, { "dimensions": ["(entrance)", "/"], "metrics": [{ "values": ["1", "1"] }] }, { "dimensions": ["(entrance)", "/page/cats/"], "metrics": [{ "values": ["1", "1"] }] }, { "dimensions": ["/turtles/turtle1.html", "/page/turtles/"], "metrics": [{ "values": ["3", "3"] }] }, { "dimensions": ["/page/turtles/", "/turtles/turtle1.html"], "metrics": [{ "values": ["3", "0"] }] }, { "dimensions": ["/page/turtles/", "/turtles/tortise.html"], "metrics": [{ "values": ["1", "1"] }] }, { "dimensions": ["/page/dogs/", "/dogs/dog_photo2.html"], "metrics": [{ "values": ["1", "0"] }] }, { "dimensions": ["/dogs/dog_photo2.html", "/page/dogs/"], "metrics": [{ "values": ["1", "1"] }] }], "totals": [{ "values": ["30", "21"] }], "rowCount": 9, "minimums": [{ "values": ["0", "0"] }], "maximums": [{ "values": ["13", "9"] }] } }]} ================================================ FILE: experiments/guess-static-sites/test/fixtures/server.js ================================================ // Simple static server for serving a page for testing front-end script const express = require('express') const cors = require('cors') const path = require('path') const fs = require('fs') const app = express() const PORT = 8080 app.use(cors()) app.get('/test.html', function (req, res) { res.sendFile(path.join(__dirname + '/test.html')) }) app.get('/predictiveFetching.js', async (req, res) => { fs.readFile(path.join(__dirname + '/../../predictiveFetching.js'), 'utf8', (err, data) => { const response = data.replace(/http:\/\/YOUR_SERVER_ENDPOINT\//g, 'http://localhost:3000/') res.send(response) }) }) app.listen(PORT, function () { console.log('Test web server listening on port ' + PORT) }) ================================================ FILE: experiments/guess-static-sites/test/fixtures/test.html ================================================ Next Page Predictor Page for testing predictive fetching. ================================================ FILE: experiments/guess-static-sites/test/gaClientTests.js ================================================ // To run tests: $ npm run generate-predictions-tests // mongod should be running const chai = require('chai') const expect = chai.expect const mongoose = require('mongoose') const config = require('../config') const parser = require('../src/parser') const fakeResponse = require('./fixtures/gaResponse.json') const Prediction = require('../src/models/prediction') describe('#SaveReports', function () { beforeEach(async () => { await mongoose.connect(config.db.mongoURL) await mongoose.connection.db.dropDatabase() }) afterEach(async () => { mongoose.disconnect() }) it('should save API response as predictions', async () => { await parser.saveReports(fakeResponse['testData']) const predictions = await Prediction.find({}) expect(await predictions.length).to.equal(4) const prediction1 = predictions.find((p) => { return p.pagePath === '/page/turtles/' }) expect(prediction1.nextPageCertainty).to.equal(0.1875) expect(prediction1.nextPagePath).to.equal('/turtles/turtle1.html') const prediction2 = predictions.find((p) => { return p.pagePath === '/page/dogs/' }) expect(prediction2.nextPageCertainty).to.equal(0.14285714285714285) expect(prediction2.nextPagePath).to.equal('/dogs/dog_photo2.html') }) }) ================================================ FILE: experiments/guess-static-sites/test/serverTests.js ================================================ // $ npm run server-tests const request = require('supertest') const express = require('express') const chai = require('chai') const mongoose = require('mongoose') const Prediction = require('../src/models/prediction') const PageView = require('../src/models/pageView') const expect = chai.expect const app = require('../server.js') const config = require('../config') describe('POST /', function () { beforeEach(async () => { await mongoose.connect(config.db.mongoURL) await mongoose.connection.db.dropDatabase() }) afterEach(async () => { mongoose.disconnect() }) describe('response', function () { describe('when a prediction for that page exists in the database', async () => { const makeRequest = function () { return request(app) .post('/') .send({ pagePath: '/page/1', userFlow: ['pf_123', 'pf_456'], clientInfo: { connectionType: '4g', platform: 'MacIntel', language: 'en-US' } }) } beforeEach(async () => { await Prediction.create({ pagePath: '/page/1', nextPagePath: '/page/2', nextPageCertainty: 0.75 }) }) it('should create a new PageView record', async () => { await makeRequest() const pageView = await PageView.findOne({}) expect(pageView.pagePath).to.equal('/page/1') expect(pageView.clientInfo.connectionType).to.equal('4g') expect(pageView.clientInfo.platform).to.equal('MacIntel') expect(pageView.clientInfo.language).to.equal('en-US') expect(pageView.userFlow[0]).to.equal('pf_123') expect(pageView.userFlow[1]).to.equal('pf_456') expect(pageView.prefetchPath).to.equal('/page/2') expect(pageView.actualNextPagePath).to.equal(undefined) }) it('should have the correct response body', async () => { const response = await makeRequest() const pageView = await PageView.findOne({pagePath: '/page/1'}) expect(response.body['pageViewId']).to.equal(pageView._id.toString()) expect(response.body['prefetchPath']).to.equal('/page/2') expect(200) }) }) describe('when a prediction for that page does not exist in the database', async () => { const makeRequest = function () { return request(app) .post('/') .send({ pagePath: '/no/matches', userFlow: [], clientInfo: { connectionType: '4g' } }) } it('should create a new PageView record', async () => { await makeRequest() const pageView = await PageView.findOne({}) expect(pageView.prefetchPath).to.equal('') expect(pageView.pagePath).to.equal('/no/matches') expect(pageView.clientInfo.connectionType).to.equal('4g') expect(pageView.userFlow.length).to.equal(0) expect(pageView.actualNextPagePath).to.equal(undefined) }) it('should have the correct response body', async () => { const response = await makeRequest() const pageView = await PageView.findOne({pagePath: '/no/matches'}) expect(response.body['pageViewId']).to.equal(pageView._id.toString()) expect(response.body['prefetchPath']).to.equal('') expect(200) }) }) }) describe('using connectionType to determine whether page should be prefetched', async () => { const makeRequestOnConnectionType = function (connectionType) { return request(app) .post('/') .send({ pagePath: '/page/1', userFlow: ['pf_123', 'pf_456'], clientInfo: { connectionType: connectionType, platform: 'MacIntel', language: 'en-US' } }) } beforeEach(async () => { await Prediction.create({ pagePath: '/page/1', nextPagePath: '/page/2', nextPageCertainty: 0.75 }) }) describe('when client is on a fast connection', async () => { it('should respond with a page to prefetch', async () => { const response = await makeRequestOnConnectionType('4g') expect(response.body['prefetchPath']).to.equal('/page/2') expect(200) }) }) describe('when client is on a slow connection', async () => { it('should not respond with a page to prefetch', async () => { const response = await makeRequestOnConnectionType('slow-2g') expect(response.body['prefetchPath']).to.equal('') expect(200) }) }) describe('when client is on an unknown connection', async () => { it('should not respond with a page to prefetch', async () => { const response = await makeRequestOnConnectionType('foobar') expect(response.body['prefetchPath']).to.equal('') expect(200) }) }) describe('when client is using Save-Data header', async () => { it('should not respond with a page to prefetch', async () => { const response = await request(app) .post('/') .set('Save-Data', true) .send({ pagePath: '/page/1', userFlow: ['pf_123', 'pf_456'], clientInfo: { connectionType: '4g', platform: 'MacIntel', language: 'en-US' } }) expect(response.body['prefetchPath']).to.equal('') expect(200) }) }) }) describe('previous pageviews', async () => { it('updates existing previous page record', async () => { const existingPageView = await PageView.create({ pagePath: '/old.html', clientInfo: { connectionType: '3g' }, userFlow: [], prefetchPath: '', actualNextPagePath: '' }) await request(app) .post('/') .send({ pagePath: '/this/page', userFlow: ['pf_5ad567927c8db21eec4ae909=1523890000', 'pf_' + existingPageView._id + '=1523891234'], clientInfo: { connectionType: '4g' } }) const updatedRecord = await PageView.findById(existingPageView._id) expect(updatedRecord.actualNextPagePath).to.equal('/this/page') }) }) }) ================================================ FILE: infra/e2e.ts ================================================ import { execSync } from 'child_process'; import { readFileSync } from 'fs'; const enterTest = 'cd packages/guess-webpack/test/fixtures/angular'; console.log(execSync(`${enterTest} && npm i`).toString()); console.log(execSync( `${enterTest} && ./node_modules/.bin/ng build --extra-webpack-config webpack.extra.js` ).toString()); // Prefetching instruction for baz const fooModule = readFileSync('packages/guess-webpack/test/fixtures/angular/dist/angular/foo-foo-module.js').toString(); if (fooModule.indexOf(`__GUESS__.p([0.6,'baz-baz-module.js'],[0.4,'qux-qux-module.js'])`) < 0) { console.error('Problem with the ordering, or cannot find prefetching instructions'); process.exit(1); } // Proper filtering of the instructions const quxModule = readFileSync('packages/guess-webpack/test/fixtures/angular/dist/angular/qux-qux-module.js').toString(); if (quxModule.indexOf(`__GUESS__.p([0.99,'foo-foo-module.js'])`) < 0) { console.error('Problem with filtering prefetching instructions'); process.exit(1); } // No prefetching instructions const bazModule = readFileSync('packages/guess-webpack/test/fixtures/angular/dist/angular/baz-baz-module.js').toString(); if (bazModule.indexOf('__GUESS__') >= 0) { console.error('Found prefetching instructions in bundle with no neighbors'); process.exit(1); } // No runtime const mainModule = readFileSync('packages/guess-webpack/test/fixtures/angular/dist/angular/main.js').toString(); if (mainModule.indexOf('__GUESS__.p(') < 0 || mainModule.indexOf('__GUESS__.p=') < 0) { console.error('Unable to find runtime or initial prefetching instruction'); process.exit(1); } // No base if (mainModule.indexOf('"http://localhost:1337"') < 0) { console.error('Unable to find the base path'); process.exit(1); } // Prod build should work console.log(execSync( `${enterTest} && ./node_modules/.bin/ng build --prod --extra-webpack-config webpack.extra.js` ).toString()); ================================================ FILE: infra/install.ts ================================================ import { join } from 'path'; import { execSync } from 'child_process'; const PackagesDir = join(process.cwd(), 'packages'); console.log( execSync( `cd ${join(PackagesDir, 'guess-parser', 'test', 'fixtures', 'angular')} && npm i` ).toString() ); ================================================ FILE: infra/pretest.ts ================================================ import { readdirSync } from 'fs'; import { join } from 'path'; import { execSync } from 'child_process'; const cwd = process.cwd(); const base = join(cwd, 'packages', 'guess-webpack', 'test', 'fixtures'); readdirSync(base).forEach(dir => { if (dir === '.' || dir === '..') { return; } execSync(`cd ${join(base, dir)} && rm -rf dist && npm i && npm run build`); }); ================================================ FILE: infra/test.ts ================================================ import { join } from 'path'; import { spawn } from 'child_process'; import chalk from 'chalk'; const StaticServer = require('static-server'); const port = 5122; const setupMockServers = () => new Promise(resolve => { const server = new StaticServer({ rootPath: join(process.cwd(), 'packages', 'guess-webpack', 'test', 'fixtures'), port }); server.start(() => { console.log(chalk.yellow('Test server started on port', server.port)); resolve(server); }); }); async function main() { await setupMockServers(); const options = process.argv.filter(a => a === '--watch'); const jest = spawn(`${process.cwd()}/node_modules/.bin/jest`, options, { stdio: 'inherit' }); return new Promise(resolve => { jest.on('exit', code => resolve(code)); jest.on('close', code => resolve(code)); }); } main().then(code => process.exit(code)); ================================================ FILE: jest-puppeteer.config.js ================================================ module.exports = { launch: { args: ['--no-sandbox', '--disable-setuid-sandbox'], headless: true } }; ================================================ FILE: jest.config.js ================================================ module.exports = { transform: { '^.+\\.tsx?$': 'ts-jest' }, testRegex: '(/test/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', testPathIgnorePatterns: [ '/packages/guess-parser/test/fixtures', '/infra/test.ts', '/experiments/guess-static-sites/test', '/packages/guess-webpack/test/fixtures' ], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], preset: '/node_modules/jest-puppeteer', globals: { window: {} } }; ================================================ FILE: lerna.json ================================================ { "lerna": "2.11.0", "packages": [ "packages/*" ], "version": "0.4.22" } ================================================ FILE: package.json ================================================ { "name": "guess", "version": "0.0.0", "description": "Smart bundling", "main": "index.js", "scripts": { "bootstrap": "npm i && lerna bootstrap", "build": "lerna run build", "publish": "lerna publish", "e2e": "ts-node infra/e2e.ts", "test": "ts-node infra/test.ts --watch", "pretest": "ts-node infra/pretest.ts", "test:ci": "ts-node infra/pretest.ts && ts-node infra/test.ts", "postinstall": "ts-node infra/install.ts" }, "repository": { "type": "git", "url": "git+https://github.com/guess-js/guess.git" }, "keywords": [ "bundling", "webpack", "ml", "ai", "analytics", "recommendation" ], "author": "Minko Gechev ", "license": "MIT", "bugs": { "url": "https://github.com/guess-js/guess/issues" }, "homepage": "https://github.com/guess-js/guess#readme", "devDependencies": { "@types/jest": "25.2.1", "@types/meow": "5.0.0", "@types/node": "9.6.60", "blund": "1.0.0", "chalk": "2.4.2", "copy-webpack-plugin": "5.1.2", "jest": "26.6.3", "jest-puppeteer": "4.4.0", "lerna": "4.0.0", "meow": "10.1.2", "prompt-confirm": "2.0.4", "puppeteer": "7.1.0", "raw-loader": "4.0.1", "static-server": "2.2.1", "ts-jest": "26.5.1", "ts-loader": "7.0.0", "ts-node": "8.8.2", "tslint": "6.1.1", "typescript": "4.5.4", "webpack": "4.46.0", "webpack-cli": "4.5.0" }, "dependencies": { "flat-cache": "^3.0.0", "googleapis": "^67.0.0" } } ================================================ FILE: packages/common/interfaces.ts ================================================ export interface Neighbors { [key: string]: number; } export interface Graph { [key: string]: Neighbors; } export interface Module { modulePath: string; parentModulePath: string; } export interface RoutingModule { path: string; modulePath: string; parentModulePath: string | null; lazy: boolean; redirectTo?: string; } export interface Connection { from: string; weight: number; to: string; } export interface Period { startDate: Date; endDate: Date; } export enum ProjectType { AngularCLI = 'angular-cli', CreateReactApp = 'create-react-app', PreactCLI = 'preact-cli', Gatsby = 'gatsby', CreateReactAppTypeScript = 'create-react-app-typescript' } export interface ProjectLayout { typescript?: string; tsconfigPath?: string; sourceDir?: string; } export interface ProjectMetadata { type: ProjectType; version: string; details?: ProjectLayout; } ================================================ FILE: packages/common/logger.ts ================================================ export enum LogLevel { DEBUG, INFO, WARN, ERROR, OFF } export class Logger { constructor(private level = LogLevel.INFO) {} setLevel(newLevel: LogLevel) { this.level = newLevel; } debug(...msg: any[]) { this.print(LogLevel.DEBUG, 'DEBUG', msg); } info(...msg: any[]) { this.print(LogLevel.INFO, 'INFO', msg); } warn(...msg: any[]) { this.print(LogLevel.WARN, 'WARN', msg); } error(...msg: any[]) { this.print(LogLevel.ERROR, 'ERROR', msg); } private print(level: LogLevel, label: string, msg: any[]) { if (level >= this.level) { switch (level) { case LogLevel.DEBUG: console.debug(this.prettify(label), ...msg); break; case LogLevel.INFO: console.info(this.prettify(label), ...msg); break; case LogLevel.WARN: console.warn(this.prettify(label), ...msg); break; case LogLevel.ERROR: console.error(this.prettify(label), ...msg); break; default: console.log(this.prettify(label), ...msg); break; } } } private prettify(label: string) { return `${label}::${Date.now()}::`; } } ================================================ FILE: packages/guess-ga/.npmignore ================================================ node_modules src test index.ts tsconfig.json webpack.config.js ================================================ FILE: packages/guess-ga/README.md ================================================ # GA Fetches data from Google analytics. ## Setup ### Create Your Credentials #### i) Create a Service Account 1. Go to the [Credentials](https://console.developers.google.com/apis/credentials) page in the Google APIs console. 2. If you don't have an existing Google Cloud project, click "Create" to create a new project. Otherwise, use the dropdown in the upper left corner to select the existing project that you'd like to use. 3. Select "Service Account key" from the "Create credentials" dropdown. 4. Fill out the form for creating a service account key: - **Service account dropdown:** Select "New Service Account". - **Service account name:** Give your service account a name. - **Role:** Select "Service Account User" ("Service Accounts" > "Service Account User"). - **Service account ID:** This field will automatically be pre-filled, but you can change this if you would like. - **Key type:** Select JSON key. *This should start a download of the credentials file* ### Configure Google Analytics #### i) Add service account to Google Analytics The service account that you just created needs to be added as a user to your Google analytics account. 1. Login to your [Google Analytics](https://analytics.google.com/analytics/web/) account. 2. Add a new user. (Admin > User Management > + > Add New Users) - **Email Address** The email address of the service account you created. It should look something like this: example@example-project-123456.iam.gserviceaccount.com. - **Permissions:** Select "Read & Analyze." *Note: A service account can only be associated with one Google Analytics account at a time. Thus, if you want to use predictive fetching on multiple sites, you'll need to create a separate service account for each.* #### ii) Enable the Google Analytics Reporting API You can enable this [here.](https://console.developers.google.com/flows/enableapi?apiid=analyticsreporting.googleapis.com&credential=client_key) ### Set up credentials To use the credentials in your project, copy it your project, and make sure to add it or its folder to gitignore. You can also opt to take `client_email` and `private_key` from the credentials file, and add them to env. ### Get your view ID To find your view ID, go to [Google Analytics](https://analytics.google.com/analytics/web/). Click the accounts dropdown (it's located in the upper lefthand corner of the screen, right next to the Google Analytics logo). The dialog that opens will have three columns: Analytics Accounts, Properties & Apps, & Views. The far right column ("Views") will contain the View ID for your site. You can opt to save this in an .env file if you do not want to share this information. ## Usage ```bash npm i guess-ga ``` Combined with `guess-parser` you can aggregate the route information and map it to your application's parametrized routes: ```ts const { fetch } = require('guess-ga'); const { parseRoutes, ProjectType } = require('guess-parser'); const { JWT } = require('google-auth-library'); const { writeFileSync } = require('fs'); const credentials = require('./secret/credentials.json'); const auth = new JWT( credentials.client_email, null, credentials.private_key, ['https://www.googleapis.com/auth/analytics.readonly'] ); const viewId = '000000000'; const applicationRoutes = parseRoutes('tsconfig.app.json', ProjectType.Angular); fetch({ auth, viewId, period: { startDate: new Date('2018-1-1'), endDate: new Date(Date.now()) }, formatter: r => r.replace('/app', ''), routes: applicationRoutes.map(f => f.path) }).then(g => { writeFileSync('data.json', JSON.stringify(g, null, 2)); }); ``` For more details visit [https://github.com/guess-js/guess](https://github.com/guess-js/guess). ## License MIT ================================================ FILE: packages/guess-ga/index.ts ================================================ export * from './src/ga'; ================================================ FILE: packages/guess-ga/package.json ================================================ { "name": "guess-ga", "version": "0.4.20", "description": "Fetch structured data from Google Analytics", "main": "dist/guess-ga/index.js", "types": "dist/guess-ga/index.d.ts", "repository": { "type": "git", "url": "git+https://github.com/guess-js/guess" }, "keywords": [ "bundling", "webpack", "ml", "ai", "google analytics", "ga", "analytics", "recommendation" ], "author": "Minko Gechev ", "license": "MIT", "bugs": { "url": "https://github.com/guess-js/guess/issues" }, "homepage": "https://github.com/guess-js/guess#readme", "dependencies": { "googleapis": "^67.0.0" }, "devDependencies": { "ts-loader": "7.0.0", "typescript": "4.5.4" }, "files": [ "dist" ], "jest": { "transform": { "^.+\\.tsx?$": "ts-jest" }, "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", "moduleFileExtensions": [ "ts", "tsx", "js", "jsx", "json", "node" ] }, "scripts": { "build": "webpack" } } ================================================ FILE: packages/guess-ga/src/client.ts ================================================ import { Period } from '../../common/interfaces'; interface PageConfig { pageToken: number | undefined; pageSize: number; } interface AnalyticsResult { report: any; nextPage: number; } const formatNumber = (n: number) => (n.toString().length === 1 ? '0' + n : n); const formatDate = (d: Date) => `${d.getFullYear()}-${formatNumber(d.getMonth() + 1)}-${formatNumber(d.getDate())}`; function requestBuilder(jwtClient: any, viewId: string, pageConfig: PageConfig, period: Period, expression: string) { return { auth: jwtClient, resource: { reportRequests: { pageSize: pageConfig.pageSize, pageToken: pageConfig.pageToken, viewId, dateRanges: [ { startDate: formatDate(period.startDate), endDate: formatDate(period.endDate) } ], dimensions: [{ name: 'ga:previousPagePath' }, { name: 'ga:pagePath' }], metrics: [{ expression }], orderBys: [{ fieldName: expression, sortOrder: 'DESCENDING' }] } } }; } async function fetchReport( client: any, jwtClient: any, viewId: string, pageConfig: PageConfig, period: Period, expression: string ) { return new Promise((resolve, reject) => { client.reports.batchGet(requestBuilder(jwtClient, viewId, pageConfig, period, expression), function( err: any, response: any ) { if (err) { reject(err); return; } const nextPage = response.data.reports[0].nextPageToken; const report = response.data.reports[0]; resolve({ report, nextPage }); }); }); } if (typeof (Symbol as any).asyncIterator === 'undefined') { (Symbol as any).asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator'); } export type GaResult = any; export interface ClientResult { error?: GaResult; report?: any; } export function getClient(jwtClient: any, pageSize: number, viewId: string, period: Period, expression: string) { const { google } = require('googleapis'); const client = google.analyticsreporting('v4'); const pageConfig: PageConfig = { pageSize, pageToken: undefined }; async function* reportGenerator(): AsyncIterableIterator { while (true) { const clientResult: ClientResult = {}; try { const result = await fetchReport(client, jwtClient, viewId, pageConfig, period, expression); clientResult.report = result.report; pageConfig.pageToken = result.nextPage; } catch (e) { clientResult.error = e; } yield clientResult; if (!pageConfig.pageToken) { break; } } } return reportGenerator; } ================================================ FILE: packages/guess-ga/src/ga.ts ================================================ import { getClient } from './client'; import { normalize } from './normalize'; import { Graph, Period } from '../../common/interfaces'; const PageSize = 10000; const id = (r: string) => r; const DefaultExpression = 'ga:pageviews'; export interface FetchConfig { auth: any; viewId: string; period: Period; formatter?: (route: string) => string; routes?: string[]; expression?: string; } export async function fetch(config: FetchConfig) { const client = getClient(config.auth, PageSize, config.viewId, config.period, config.expression || DefaultExpression); const graph: Graph = {}; for await (const val of client()) { if (val.error) { throw val.error; } const result = val.report; normalize(result.data, config.formatter || id, config.routes || []).forEach((n: any) => { const r = graph[n.from] || {}; r[n.to] = n.weight + (r[n.to] || 0); graph[n.from] = r; }); } return graph; } ================================================ FILE: packages/guess-ga/src/normalize.ts ================================================ import { Connection } from '../../common/interfaces'; export const matchRoute = (route: string, declaration: string): boolean => { const routeParts: string[] = route.split('/'); const declarationParts: string[] = declaration.split('/'); if (routeParts.length !== declarationParts.length) { return false; } else { return declarationParts.reduce((a: boolean, p: string, i: number) => { if (p.startsWith(':')) { return a; } return a && p === routeParts[i]; }, true); } }; const findRoute = (d: string[], r: string) => d.filter(def => def.indexOf(':') < 0).find(c => matchRoute(r, c)) || d.find(c => matchRoute(r, c)) || r; const processRoute = (declarations: string[], route: string) => findRoute(declarations, route.split('?')[0]); export const normalize = (data: any, formatter: (s: string) => string, declarations: string[]) => { return (data.rows || []) .map((r: any) => { return { from: processRoute(declarations, formatter(r.dimensions[0])), to: processRoute(declarations, formatter(r.dimensions[1])), weight: parseInt(r.metrics[0].values[0], 10) }; }) .filter((node: Connection) => node.from !== '(entrance)' && node.from !== node.to); }; ================================================ FILE: packages/guess-ga/test/normalize.spec.ts ================================================ import { matchRoute, normalize } from '../src/normalize'; const GAData = { rows: [ { dimensions: ['/a', '/b'], metrics: [ { values: [1] } ] }, { dimensions: ['/a/4', '/c'], metrics: [ { values: [1] } ] }, { dimensions: ['/a/3', '/c'], metrics: [ { values: [1] } ] }, { dimensions: ['/a', '/a/3'], metrics: [ { values: [1] } ] } ] }; describe('matchRoute', () => { it('should work simple routes', () => { expect(matchRoute('/', '/')).toBeTruthy(); expect(matchRoute('/foo', '/foo')).toBeTruthy(); expect(matchRoute('/bar/baz', '/bar/baz')).toBeTruthy(); }); it('should work with parameters', () => { expect(matchRoute('/bar', '/:random')).toBeTruthy(); expect(matchRoute('/foo/1', '/foo/:id')).toBeTruthy(); }); it('should fail with trailing slash', () => { expect(matchRoute('/foo', '/foo/')).toBeFalsy(); }); }); describe('normalize', () => { it('should work without a trailing slash', () => { const normalized = normalize(GAData, a => a, ['/a/', '/b', '/a/:id', 'c']); expect(normalized).toEqual([ { from: '/a', to: '/b', weight: 1 }, { from: '/a/:id', to: '/c', weight: 1 }, { from: '/a/:id', to: '/c', weight: 1 }, { from: '/a', to: '/a/:id', weight: 1 } ]); }); }); ================================================ FILE: packages/guess-ga/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "module": "commonjs", "sourceMap": true, "declaration": true, "experimentalDecorators": true, "strict": true, "outDir": "dist", "downlevelIteration": true, "lib": ["es2015", "esnext", "dom"] }, "files": ["index.ts"] } ================================================ FILE: packages/guess-ga/webpack.config.js ================================================ module.exports = { mode: 'production', entry: './index.ts', target: 'node', output: { filename: './guess-ga/index.js', libraryTarget: 'umd' }, externals: [/^(@|\w{3}(?
Intro Main
import('./intro/Intro'))} />
``` Currently, there are several important conventions: * Support only for JSX syntax * Support only for `react-router`-like syntax * The path attribute of the `` element must have value of type string literal. * The lazy-loaded components should have dynamic import with the following structure of the AST: * `CallExpression` (e.g. `AsyncComponent`) with a single argument * The type of the argument should be an `ArrowFunction` * The arrow function should have an expression as body (e.g. `CallExpression`) * To the `CallExpression` should be passed a `StringLiteral` which points to the lazy-loaded module **Contributions aiming to extend the supported syntax are very welcome!** ## License MIT ================================================ FILE: packages/guess-parser/index.ts ================================================ export { parseRoutes } from './src/parser'; export { detect } from './src/detector'; export { parseRoutes as parseAngularRoutes } from './src/angular'; export * from './src/react'; export * from './src/preact'; ================================================ FILE: packages/guess-parser/package.json ================================================ { "name": "guess-parser", "version": "0.4.22", "description": "Finds the route declarations in your application.", "main": "dist/guess-parser/index.js", "types": "dist/guess-parser/index.d.ts", "repository": { "type": "git", "url": "git+https://github.com/guess-js/guess" }, "keywords": [ "bundling", "webpack", "ml", "ai", "analytics", "recommendation" ], "author": "Minko Gechev ", "license": "MIT", "bugs": { "url": "https://github.com/guess-js/guess/issues" }, "homepage": "https://github.com/guess-js/guess#readme", "dependencies": { "ts-evaluator": "0.1.0" }, "devDependencies": { "ts-loader": "7.0.0" }, "peerDependencies": { "typescript": ">=3.7.5" }, "files": [ "dist" ], "jest": { "transform": { "^.+\\.tsx?$": "ts-jest" }, "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", "moduleFileExtensions": [ "ts", "tsx", "js", "jsx", "json", "node" ] }, "scripts": { "build": "webpack" } } ================================================ FILE: packages/guess-parser/src/angular/index.ts ================================================ export * from './route-parser'; ================================================ FILE: packages/guess-parser/src/angular/modules.ts ================================================ import * as ts from 'typescript'; import { resolve, join, dirname, sep } from 'path'; import { existsSync } from 'fs'; import { LazyRoute, Route, readLoadChildren } from './routes'; import { RoutingModule } from '../../../common/interfaces'; interface RoutesDeclaration { lazyRoutes: LazyRoute[]; eagerRoutes: Route[]; } export interface Registry { [path: string]: RoutesDeclaration; } export const findRootModule = (registry: Registry): string => { const childModules = new Set(); const traverseRoute = (route: Route) => { if ((route as LazyRoute).module) { childModules.add((route as LazyRoute).module); } route.children.forEach(traverseRoute); }; const allModulePaths = Object.keys(registry); allModulePaths.forEach(path => { const declaration = registry[path]; // It's possible if the declaration does not exist // See https://github.com/guess-js/guess/issues/311 if (declaration) { declaration.eagerRoutes.forEach(traverseRoute); declaration.lazyRoutes.forEach(traverseRoute); } }); const roots = allModulePaths.filter(m => !childModules.has(m)); if (roots.length > 1) { throw new Error('Multiple root routing modules found ' + roots.join(', ')); } return roots[0]; }; export const collectRoutingModules = ( rootFile: string, registry: Registry, result: RoutingModule[], parentFilePath: string = rootFile, currentRoutePath: string = '', existing = new Set() ) => { const declaration = registry[rootFile]; // It's possible if the declaration does not exist // See https://github.com/guess-js/guess/issues/311 if (!declaration) { return; } const process = (r: Route, routePath = currentRoutePath) => { if ((r as LazyRoute).module) { // tslint:disable-next-line: no-use-before-declare return processLazyRoute(r as LazyRoute, routePath); } // tslint:disable-next-line: no-use-before-declare return processRoute(r, routePath); }; const processRoute = (r: Route, routePath = currentRoutePath) => { const path = (routePath + '/' + r.path).replace(/\/$/, ''); r.children.forEach(route => process(route, path)); if (!existing.has(path)) { const routingModule: RoutingModule = { path, lazy: parentFilePath !== rootFile && r.redirectTo === undefined, modulePath: rootFile, parentModulePath: parentFilePath, }; if (r.redirectTo !== undefined) { routingModule.redirectTo = r.redirectTo; } result.push(routingModule); existing.add(path); } }; const processLazyRoute = (r: LazyRoute, routePath = currentRoutePath) => { const path = (routePath + '/' + r.path).replace(/\/$/, ''); r.children.forEach(route => process(route, path)); collectRoutingModules(r.module, registry, result, rootFile, path); }; declaration.eagerRoutes.forEach(r => processRoute(r)); declaration.lazyRoutes.forEach(r => processLazyRoute(r)); }; export const findMainModule = (program: ts.Program) => { const tryFindMainModule = (n: ts.Node, sf: ts.SourceFile) => { if ( n.kind === ts.SyntaxKind.Identifier && (n as ts.Identifier).text === 'bootstrapModule' ) { const propAccess = (n as ts.Identifier).parent; if ( !propAccess || propAccess.kind !== ts.SyntaxKind.PropertyAccessExpression ) { return null; } const tempExpr = propAccess.parent; if (!tempExpr || tempExpr.kind !== ts.SyntaxKind.CallExpression) { return null; } const expr = tempExpr as ts.CallExpression; const module = expr.arguments[0]; const tc = program.getTypeChecker(); const symbol = tc.getTypeAtLocation(module).getSymbol(); if (!symbol) { return null; } const decl = symbol.getDeclarations(); if (!decl) { return null; } return resolve(decl[0].getSourceFile().fileName); } let mainPath: null | string = null; n.forEachChild(c => { if (mainPath) { return mainPath; } mainPath = tryFindMainModule(c, sf); }); return mainPath; }; return program.getSourceFiles().reduce((a, sf) => { if (a) { return a; } let mainPath: null | string = null; sf.forEachChild(n => { if (mainPath) { return; } mainPath = tryFindMainModule(n, sf); }); return mainPath; }, null); }; const isImportDeclaration = (node: ts.Node): node is ts.ImportDeclaration => { return node.kind === ts.SyntaxKind.ImportDeclaration; }; const isReExportDeclaration = (node: ts.Node): node is ts.ExportDeclaration => { return (node.kind === ts.SyntaxKind.ExportDeclaration && (node as ts.ExportDeclaration).exportClause === undefined); }; const normalizeFilePath = (path: string): string => { return join(...path.split(/\//).map((part, index) => (part === '' && index === 0) ? sep : part)); }; export const getModulePathFromRoute = (parentPath: string, loadChildren: string, program: ts.Program, host: ts.CompilerHost) => { const childModule = loadChildren.split('#')[0]; const { resolvedModule } = ts.resolveModuleName(childModule, parentPath, program.getCompilerOptions(), host); if (resolvedModule) { return normalizeFilePath(resolvedModule.resolvedFileName); } const childModuleFile = childModule + '.ts'; const parentSegments = dirname(parentPath).split(sep); const childSegments = childModuleFile.split('/'); const max = Math.min(parentSegments.length, childSegments.length); let maxCommon = 0; for (let i = 1; i < max; i += 1) { for (let j = 0; j < i; j += 1) { let common = 0; if (parentSegments[parentSegments.length - 1 - j] === childSegments[j]) { common++; maxCommon = Math.max(maxCommon, common); } else { // breaking here common = 0; j = i; } } } const path = join( dirname(parentPath), childModuleFile .split('/') .slice(maxCommon, childSegments.length) .join('/') ); // This early failure provides better error message compared to the // generic "Multiple root routing modules" error. if (!existsSync(path)) { throw new Error(`The relative path "${loadChildren}" to "${parentPath}" cannot be resolved to a module`); } return path; }; const imports = ( parent: string, child: string, program: ts.Program, host: ts.CompilerHost, importCache: {[parent: string]: {[child: string]: boolean}}, visited: { [key: string]: boolean } = {} ) => { if (importCache[parent] && importCache[parent][child] !== undefined) { return importCache[parent][child]; } importCache[parent] = importCache[parent] || {}; const sf = program.getSourceFile(parent); if (!sf) { importCache[parent][child] = false; return false; } if (visited[parent]) { importCache[parent][child] = false; return false; } visited[parent] = true; let found = false; sf.forEachChild(n => { if (found) { return; } if (!isImportDeclaration(n) && !isReExportDeclaration(n)) { return; } const path = (n.moduleSpecifier as ts.StringLiteral).text; const { resolvedModule } = ts.resolveModuleName(path, parent, program.getCompilerOptions(), host); if (resolvedModule === undefined) { return; } const fullPath = normalizeFilePath(resolvedModule.resolvedFileName); if (fullPath === child) { found = true; } // We don't want to dig into node_modules to find an entry point. if (!found && existsSync(fullPath) && !fullPath.includes('node_modules')) { found = imports(fullPath, child, program, host, importCache, visited); } }); importCache[parent][child] = found; return found; }; let cache: { [parent: string]: { [child: string]: boolean } } = {}; export const cleanModuleCache = () => (cache = {}); // This can potentially break if there's a lazy module // that is not only loaded lazily but also imported // inside of a parent module. // // For example, `app.module.ts` lazily loads `bar.module.ts` // in the same time `app.module.ts` imports `bar.module.ts` // this way the module entry point will be `app.module.ts`. export const getModuleEntryPoint = ( path: string, entryPoints: Set, program: ts.Program, host: ts.CompilerHost ): string => { const parents = [...entryPoints].filter(e => imports(e, path, program, host, cache)); // If no parents, this could be the root module if (parents.length === 0) { return path; } if (parents.length > 1) { throw new Error( `Module ${path} belongs to more than one module: ${parents.join(', ')}` ); } return parents[0]; }; export const getLazyEntryPoints = ( node: ts.ObjectLiteralExpression, program: ts.Program, host: ts.CompilerHost ) => { const value = readLoadChildren(node, program.getTypeChecker()); if (!value) { return null; } const parent = resolve(node.getSourceFile().fileName); const module = getModulePathFromRoute(parent, value, program, host); return module; }; ================================================ FILE: packages/guess-parser/src/angular/route-parser.ts ================================================ import * as ts from 'typescript'; import { RoutingModule } from '../../../common/interfaces'; import { existsSync, readFileSync } from 'fs'; import { dirname, resolve, join } from 'path'; import { findMainModule, getLazyEntryPoints, getModuleEntryPoint, Registry, collectRoutingModules, findRootModule, cleanModuleCache } from './modules'; import { LazyRoute, isRoute, getRoute, readChildren } from './routes'; export interface Options { redirects: boolean; } const defaultOptions: Options = { redirects: false, }; const normalizeOptions = (options: Partial) => ({ ...defaultOptions, ...options, }); export const parseRoutes = ( tsconfig: string, exclude: string[] = [], inputOptions: Partial = {} ): RoutingModule[] => { const options = normalizeOptions(inputOptions); cleanModuleCache(); const parseConfigHost: ts.ParseConfigHost = { fileExists: existsSync, readDirectory: ts.sys.readDirectory, readFile: file => readFileSync(file, 'utf8'), useCaseSensitiveFileNames: true }; const config = ts.readConfigFile(tsconfig, path => readFileSync(path).toString() ); const parsed = ts.parseJsonConfigFileContent( config.config, parseConfigHost, resolve(dirname(tsconfig)), { noEmit: true } ); const host = ts.createCompilerHost(parsed.options, true); const program = ts.createProgram(parsed.fileNames, parsed.options, host); const typeChecker = program.getTypeChecker(); const toAbsolute = (file: string) => file.startsWith('/') || file.startsWith(process.cwd()) ? file : join(process.cwd(), file); const excludeFiles = new Set(exclude.map(toAbsolute)); const visitTopLevelRoutes = ( s: ts.SourceFile, callback: (routeObj: ts.Node) => void, n: ts.Node ) => { if (excludeFiles.has(resolve(s.fileName))) { return; } if (!n) { return; } if (isRoute(n, typeChecker, options.redirects)) { callback(n); } else { n.forEachChild(visitTopLevelRoutes.bind(null, s, callback)); } }; const mainPath = findMainModule(program); if (!mainPath) { throw new Error('Cannot find the main application module'); } const entryPoints: Set = new Set([mainPath]); const collectEntryPoints = (n: ts.Node) => { const path = getLazyEntryPoints( n as ts.ObjectLiteralExpression, program, host ); if (!path) { const childrenArray = readChildren(n as ts.ObjectLiteralExpression); if (childrenArray) { childrenArray.forEach(collectEntryPoints); } return; } entryPoints.add(path); }; program.getSourceFiles().map(s => { s.forEachChild( visitTopLevelRoutes.bind(null, s, collectEntryPoints) ); }); const registry: Registry = {}; program.getSourceFiles().map(s => { s.forEachChild( visitTopLevelRoutes.bind(null, s, (n: ts.Node) => { const path = resolve(n.getSourceFile().fileName); const route = getRoute( n as ts.ObjectLiteralExpression, entryPoints, program, host ); if (!route) { return; } const modulePath = getModuleEntryPoint(path, entryPoints, program, host); const current = registry[modulePath] || { lazyRoutes: [], eagerRoutes: [] }; if ((route as LazyRoute).module) { current.lazyRoutes.push(route as LazyRoute); } else { current.eagerRoutes.push(route); } registry[modulePath] = current; }) ); }); const result: RoutingModule[] = []; if (Object.keys(registry).length > 0) { collectRoutingModules(findRootModule(registry), registry, result); } return result; }; ================================================ FILE: packages/guess-parser/src/angular/routes.ts ================================================ import * as ts from 'typescript'; import { evaluate } from 'ts-evaluator'; import { getModuleEntryPoint, getModulePathFromRoute } from './modules'; import { resolve } from 'path'; const getObjectProp = ( node: ts.ObjectLiteralExpression, prop: string ): ts.Expression | null => { const vals = node.properties.values(); for (const val of vals) { if (val.kind !== ts.SyntaxKind.PropertyAssignment) { continue; } const value = val as ts.PropertyAssignment; if (value.name.kind !== ts.SyntaxKind.Identifier) { continue; } const name = value.name.text; if (name === prop) { return value.initializer; } } return null; }; export const readLoadChildren = ( node: ts.ObjectLiteralExpression, typeChecker: ts.TypeChecker ): string | null => { const expr = getObjectProp(node, 'loadChildren'); if (!expr) { return null; } if (expr.kind === ts.SyntaxKind.StringLiteral) { return (expr as ts.StringLiteral).text; } let result: string | null = null; const visitor = (n: ts.Node) => { if (n.kind === ts.SyntaxKind.ImportKeyword) { const parent = n.parent as ts.CallExpression; const arg = parent.arguments[0]; const res = evaluate({ node: arg, typeChecker: typeChecker }); if (res.success) { result = res.value as string; } } if (result) { return; } n.forEachChild(visitor); }; expr.forEachChild(visitor); // Fallback to when loadChildren looks like: // loadChildren: 'foo' + '/' + 'bar' if (!result) { const res = evaluate({ node: expr, typeChecker: typeChecker }); if (res.success) { result = res.value as string; } } return result; }; const readPath = ( node: ts.ObjectLiteralExpression, typeChecker: ts.TypeChecker ): string | null => { const expr = getObjectProp(node, 'path'); if (!expr) { return null; } const val = evaluate({ node: expr, typeChecker }); if (val.success) { return val.value as string; } return null; }; const readRedirect = ( node: ts.ObjectLiteralExpression, typeChecker: ts.TypeChecker ): string | null => { const expr = getObjectProp(node, 'redirectTo'); if (!expr) { return null; } const val = evaluate({ node: expr, typeChecker }); if (val.success) { return val.value as string; } return null; }; export const readChildren = ( node: ts.ObjectLiteralExpression, ): ts.NodeArray | null => { const expr = getObjectProp(node, 'children'); if (!expr) { return null; } return (expr as ts.ArrayLiteralExpression).elements; }; export interface Route { path: string; children: Route[]; redirectTo?: string; } export interface LazyRoute extends Route { module: string; } export const getRoute = ( node: ts.ObjectLiteralExpression, entryPoints: Set, program: ts.Program, host: ts.CompilerHost ): Route | null => { const path = readPath(node, program.getTypeChecker()); if (path === null) { return null; } const childrenArray = readChildren(node); let children: Route[] = []; if (childrenArray) { children = childrenArray .map(c => { if (c.kind !== ts.SyntaxKind.ObjectLiteralExpression) { return null; } return getRoute(c as ts.ObjectLiteralExpression, entryPoints, program, host); }) .filter(e => e !== null) as Route[]; } const route: Route = { path, children }; const redirectTo = readRedirect(node, program.getTypeChecker()); if (redirectTo) { route.redirectTo = redirectTo; } const loadChildren = readLoadChildren(node, program.getTypeChecker()); if (loadChildren) { const parent = getModuleEntryPoint( resolve(node.getSourceFile().fileName), entryPoints, program, host ); const module = getModulePathFromRoute(parent, loadChildren, program, host); return { ...route, module } as LazyRoute; } return route; }; export const isRoute = (n: ts.Node, typeChecker: ts.TypeChecker, redirects: boolean): boolean => { if ( n.kind !== ts.SyntaxKind.ObjectLiteralExpression || !n.parent || n.parent.kind !== ts.SyntaxKind.ArrayLiteralExpression ) { return false; } const objLiteral = n as ts.ObjectLiteralExpression; const path = readPath(objLiteral, typeChecker) !== null; const redirectTo = redirects && readRedirect(objLiteral, typeChecker) !== null; const children = !!readChildren(objLiteral); const loadChildren = !!readLoadChildren(objLiteral, typeChecker); const component = !!getObjectProp(objLiteral, 'component'); return (path && children) || (path && component) || (path && loadChildren) || (path && redirectTo); }; ================================================ FILE: packages/guess-parser/src/detector/detect.ts ================================================ import { readFileSync, existsSync } from 'fs'; import { join } from 'path'; import { ProjectType, ProjectMetadata } from '../../../common/interfaces'; const dep = (p: any) => (name: string) => (p.dependencies ? p.dependencies[name] : undefined); const devDep = (p: any) => (name: string) => (p.devDependencies ? p.devDependencies[name] : undefined); export const detect = (base: string): ProjectMetadata | undefined => { const path = ['package.json', '../package.json'] .map(p => join(base, p)) .filter(existsSync) .pop(); if (!path) { throw new Error('Unable to discover the project type'); } const content = JSON.parse(readFileSync(path).toString()) as any; const exists = (file: string) => existsSync(join(base, file)); const d = dep(content); const dd = devDep(content); const tsconfig = 'tsconfig.app.json'; const srcTsConfig = join('src', tsconfig); if (dd('@angular/cli') && (exists(srcTsConfig) || exists(tsconfig))) { let tsconfigPath = tsconfig; if (exists(srcTsConfig)) { tsconfigPath = srcTsConfig; } return { type: ProjectType.AngularCLI, version: dd('@angular/cli'), details: { typescript: dd('typescript'), tsconfigPath, sourceDir: 'src' } }; } if (d('gatsby')) { return { type: ProjectType.Gatsby, version: d('gatsby') }; } if (d('react') && d('react-scripts-ts') && exists('tsconfig.json')) { return { type: ProjectType.CreateReactAppTypeScript, version: d('react-scripts-ts'), details: { typescript: dd('typescript'), tsconfigPath: join(base, 'tsconfig.json'), sourceDir: 'src' } }; } if (d('react') && d('react-scripts')) { return { type: ProjectType.CreateReactApp, version: d('react-scripts'), details: { sourceDir: 'src' } }; } if (d('preact') && dd('preact-cli')) { return { type: ProjectType.PreactCLI, version: dd('preact-cli'), details: { sourceDir: '.' } }; } return undefined; }; ================================================ FILE: packages/guess-parser/src/detector/index.ts ================================================ export * from './detect'; ================================================ FILE: packages/guess-parser/src/language-service.ts ================================================ import { existsSync, readFileSync } from 'fs'; import * as ts from 'typescript'; export const getLanguageService = (rootFileNames: string[], options: ts.CompilerOptions) => { const files: ts.MapLike<{ version: number }> = {}; // initialize the list of files rootFileNames.forEach(fileName => { files[fileName] = { version: 0 }; }); const servicesHost: ts.LanguageServiceHost = { getScriptFileNames: () => rootFileNames, getScriptVersion: fileName => files[fileName] && files[fileName].version.toString(), getScriptSnapshot: fileName => { if (!existsSync(fileName)) { return undefined; } return ts.ScriptSnapshot.fromString(readFileSync(fileName).toString()); }, getCurrentDirectory: () => process.cwd(), getCompilationSettings: () => options, getDefaultLibFileName: o => ts.getDefaultLibFilePath(o) }; return ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); }; ================================================ FILE: packages/guess-parser/src/parser.ts ================================================ import { parseRoutes as ngParseRoutes } from './angular'; import { parseReactTSXRoutes, parseReactJSXRoutes } from './react'; import { parsePreactJSXRoutes } from './preact'; import { RoutingModule, ProjectType } from '../../common/interfaces'; import { detect } from './detector'; import { join } from 'path'; const unique = (a: RoutingModule[]) => { const map: { [path: string]: RoutingModule } = {}; a.forEach(r => (map[r.path] = r)); return Object.keys(map).map(k => map[k]); }; export const parseRoutes = (base: string) => { let result: RoutingModule[] | undefined = undefined; const app = detect(base); if (!app) { throw new Error('Cannot detect the application type'); } if (app.type === ProjectType.AngularCLI && app.details && app.details.tsconfigPath) { result = ngParseRoutes(join(base, app.details.tsconfigPath)); } if (app.type === ProjectType.CreateReactAppTypeScript && app.details && app.details.tsconfigPath) { result = parseReactTSXRoutes(app.details.tsconfigPath); } if (app.type === ProjectType.CreateReactApp && app.details && app.details.sourceDir) { result = parseReactJSXRoutes(join(base, app.details.sourceDir)); } if (app.type === ProjectType.PreactCLI && app.details && app.details.sourceDir) { result = parsePreactJSXRoutes(join(base, app.details.sourceDir)); } if (!result) { throw new Error('Unknown project type'); } const res = unique(result); res.filter(r => !r.parentModulePath || r.path === '/').forEach(r => (r.parentModulePath = null)); return res; }; ================================================ FILE: packages/guess-parser/src/preact/index.ts ================================================ import * as ts from 'typescript'; import * as path from 'path'; import { RoutingModule } from '../../../common/interfaces'; import { readFiles } from '../utils'; import { getLanguageService } from '../language-service'; const LazyRe = /routes\/((\w+\/index)|\w+)\.(js|jsx|ts|tsx)$/; const getLazyDefinition = ( filename: string, identifier: ts.Node, ls: ts.LanguageService ): ts.DefinitionInfo | undefined => { const defs = ls.getDefinitionAtPosition(filename, identifier.pos + 1); if (!defs) { return undefined; } return defs.filter(d => LazyRe.test(d.fileName) && d.kind === 'class').pop(); }; const extractModule = (a: ts.JsxAttribute) => { const init = a.initializer as ts.JsxExpression | null; if (!init) { return null; } const arrow = init.expression as ts.ArrowFunction | null; if (!arrow) { return ''; } const body = arrow.body as ts.CallExpression | null; if (!body) { return ''; } const temp = body.expression as ts.CallExpression | null; if (!temp) { return ''; } const internalExpr = temp.expression as ts.CallExpression | null; if (!internalExpr) { return ''; } const arg = internalExpr.arguments[0] as ts.StringLiteral | null; if (!arg || arg.kind !== ts.SyntaxKind.StringLiteral) { return ''; } return (arg as ts.StringLiteral).text; }; const extractRoute = (c: ts.Node, file: ts.SourceFile, ls: ts.LanguageService) => { if (c.kind !== ts.SyntaxKind.JsxElement && c.kind !== ts.SyntaxKind.JsxSelfClosingElement) { return; } let el: ts.JsxSelfClosingElement | ts.JsxOpeningElement = (c as ts.JsxElement).openingElement; if (c.kind === ts.SyntaxKind.JsxSelfClosingElement) { el = c as ts.JsxSelfClosingElement; } const module: Partial = { lazy: (el.tagName as ts.Identifier).text === 'AsyncRoute', parentModulePath: file.fileName, modulePath: file.fileName }; const def = getLazyDefinition(file.getSourceFile().fileName, el, ls); if (def) { module.lazy = true; module.modulePath = def.fileName; } el.attributes.properties.forEach(p => { const { text } = p.name as ts.Identifier; if (text === 'path') { module.path = ((p as ts.JsxAttribute).initializer as ts.StringLiteral).text; } if (text === 'getComponent') { const parts = file.fileName.split('/'); parts.pop(); const tempName = extractModule(p as ts.JsxAttribute); if (tempName) { const name = tempName + '.tsx'; module.modulePath = '/' + path.join(...parts.concat([name])); module.lazy = true; } } }); return module as RoutingModule; }; const extractRoutes = (file: ts.SourceFile, ls: ts.LanguageService): RoutingModule[] => { const result: RoutingModule[] = []; const stack: ts.Node[] = [file]; while (stack.length) { const c = stack.pop(); if (!c) { return result; } const el: ts.JsxSelfClosingElement | ts.JsxOpeningElement = (c as ts.JsxElement).openingElement; if (c.kind === ts.SyntaxKind.JsxElement && (el.tagName as ts.Identifier).text === 'Router') { (c as ts.JsxElement).children.forEach((e: ts.Node) => { const route = extractRoute(e, file, ls); if (route) { result.push(route); } }); } else { c.getChildren(file).forEach(child => stack.push(child)); } } return result; }; export const parsePreactJSXRoutes = (base: string): RoutingModule[] => { const options = { jsx: ts.JsxEmit.React, allowJs: true }; const rootFileNames = readFiles(base); const program = ts.createProgram(rootFileNames, options); const jsxFiles = program .getSourceFiles() .filter(f => f.fileName.endsWith('.tsx') || f.fileName.endsWith('.jsx') || f.fileName.endsWith('.js')); const routes = jsxFiles.reduce( (a, f) => a.concat(extractRoutes(f, getLanguageService(rootFileNames, options))), [] as RoutingModule[] ); const routeMap = routes.reduce( (a, m) => { a[m.path] = m; return a; }, {} as { [key: string]: RoutingModule } ); return Object.keys(routeMap).map(k => routeMap[k]); }; ================================================ FILE: packages/guess-parser/src/react/base.ts ================================================ import * as ts from 'typescript'; import * as path from 'path'; import { RoutingModule } from '../../../common/interfaces'; const extractRoutes = (file: ts.SourceFile): RoutingModule[] => { const result: RoutingModule[] = []; const stack: ts.Node[] = [file]; const extractModule = (a: ts.JsxAttribute) => { const init = a.initializer as ts.JsxExpression | null; if (!init) { return null; } const expr = init.expression as ts.CallExpression | null; if (!expr) { return ''; } if (!expr.arguments) { return ''; } const arrow = expr.arguments[0] as ts.ArrowFunction | null; if (!arrow) { return ''; } const body = arrow.body as ts.CallExpression; if (!body) { return ''; } const arg = body.arguments[0]; if (!arg || arg.kind !== ts.SyntaxKind.StringLiteral) { return ''; } return (arg as ts.StringLiteral).text; }; while (stack.length) { const c = stack.pop(); if (!c) { return result; } if (c.kind === ts.SyntaxKind.JsxElement || c.kind === ts.SyntaxKind.JsxSelfClosingElement) { let el: ts.JsxSelfClosingElement | ts.JsxOpeningElement = (c as ts.JsxElement).openingElement; if (c.kind === ts.SyntaxKind.JsxSelfClosingElement) { el = c as ts.JsxSelfClosingElement; } if ((el.tagName as ts.Identifier).text === 'Route') { const module: Partial = { lazy: false, parentModulePath: file.fileName, modulePath: file.fileName }; el.attributes.properties.forEach(p => { const { text } = p.name as ts.Identifier; if (text === 'path') { module.path = ((p as ts.JsxAttribute).initializer as ts.StringLiteral).text; } if (text === 'component') { const parts = file.fileName.split('/'); parts.pop(); const tempName = extractModule(p as ts.JsxAttribute); if (tempName) { const name = tempName + '.tsx'; module.modulePath = '/' + path.join(...parts.concat([name])); module.lazy = true; } } result.push(module as RoutingModule); }); } } c.getChildren(file).forEach(child => { stack.push(child); }); } return result; }; export const parseReactRoutes = (files: string[], options: ts.CompilerOptions) => { const program = ts.createProgram(files, options); const jsxFiles = program.getSourceFiles().filter(f => f.fileName.endsWith('.tsx') || f.fileName.endsWith('.jsx')); const routes = jsxFiles.reduce((a, f) => a.concat(extractRoutes(f)), [] as RoutingModule[]); const modules = routes.reduce( (a, r) => { a[r.modulePath] = true; return a; }, {} as { [key: string]: boolean } ); const rootModule = routes.filter(r => r.parentModulePath && !modules[r.parentModulePath]).pop(); if (rootModule) { routes.push({ path: '/', parentModulePath: null, modulePath: rootModule.parentModulePath, lazy: false } as RoutingModule); } const routeMap = routes.reduce( (a, m) => { a[m.path] = m; return a; }, {} as { [key: string]: RoutingModule } ); return Object.keys(routeMap).map(k => routeMap[k]); }; ================================================ FILE: packages/guess-parser/src/react/index.ts ================================================ export { parseReactRoutes } from './base'; export { parseRoutes as parseReactTSXRoutes } from './react-tsx'; export { parseRoutes as parseReactJSXRoutes } from './react-jsx'; ================================================ FILE: packages/guess-parser/src/react/react-jsx.ts ================================================ import { parseReactRoutes } from '.'; import { JsxEmit } from 'typescript'; import { RoutingModule } from '../../../common/interfaces'; import { readFiles } from '../utils'; export const parseRoutes = (base: string): RoutingModule[] => { return parseReactRoutes(readFiles(base), { jsx: JsxEmit.React, allowJs: true }); }; ================================================ FILE: packages/guess-parser/src/react/react-tsx.ts ================================================ import { readFileSync, lstatSync, existsSync } from 'fs'; import * as ts from 'typescript'; import { join, dirname, resolve } from 'path'; import { parseReactRoutes } from './'; import { RoutingModule } from '../../../common/interfaces'; const parseConfigHost = { useCaseSensitiveFileNames: true, fileExists: existsSync, readDirectory: ts.sys.readDirectory, readFile: ts.sys.readFile }; const calcProjectFileAndBasePath = (project: string): { projectFile: string; basePath: string } => { const projectIsDir = lstatSync(project).isDirectory(); const projectFile = projectIsDir ? join(project, 'tsconfig.json') : project; const projectDir = projectIsDir ? project : dirname(project); const basePath = resolve(process.cwd(), projectDir); return { projectFile, basePath }; }; export const parseRoutes = (tsconfig: string): RoutingModule[] => { const { config, error } = ts.readConfigFile(tsconfig, (f: string) => readFileSync(f).toString()); if (error) { throw error; } const { basePath } = calcProjectFileAndBasePath(tsconfig); const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, basePath); return parseReactRoutes(parsed.fileNames, parsed.options); }; ================================================ FILE: packages/guess-parser/src/utils.ts ================================================ import { statSync, readdirSync } from 'fs'; import { join } from 'path'; export const readFiles = (dir: string): string[] => { if (dir === 'node_modules') { return []; } const result = readdirSync(dir).map(node => join(dir, node)); const files = result.filter( node => statSync(node).isFile() && (node.endsWith('.jsx') || node.endsWith('.js')) ); const dirs = result.filter(node => statSync(node).isDirectory()); return [].concat.apply(files, (dirs.map(readFiles) as unknown) as ConcatArray[]); }; ================================================ FILE: packages/guess-parser/test/angular.spec.ts ================================================ import { parseRoutes } from '../src/angular'; const fixtureRoutes = new Set([ '/foo', '/foo/baz', '/foo/index', '/foo/baz/index', '/bar/baz', '/qux', '/library', '/bar-simple', '/foo/child1', '/foo/foo-parent', '/foo/foo-parent/child2', '/eager', '/eager/lazy', ]); const fixtureRoutesWithRedirects = new Set([ ...fixtureRoutes, '' ]); const nxRoutes = new Set([ '/login', '/home', '/customers/list', '/customers' ]); const nxRoutesWithRedirects = new Set([ ...nxRoutes, '' ]); describe('Angular parser', () => { it('should parse an app', () => { expect(() => parseRoutes( 'packages/guess-parser/test/fixtures/angular/src/tsconfig.app.json' ) ).not.toThrow(); }); it('should produce routes', () => { const routes = parseRoutes( 'packages/guess-parser/test/fixtures/angular/src/tsconfig.app.json' ); expect(routes instanceof Array).toBeTruthy(); const allRoutes = new Set(routes.map(r => r.path)); [...allRoutes].forEach(r => expect(fixtureRoutes).toContain(r)); expect(allRoutes.size).toEqual(fixtureRoutes.size); }); it('should consider redirects', () => { const routes = parseRoutes( 'packages/guess-parser/test/fixtures/angular/src/tsconfig.app.json' , undefined, { redirects: true }); expect(routes instanceof Array).toBeTruthy(); const allRoutes = new Set(routes.map(r => r.path)); [...allRoutes].forEach(r => expect(fixtureRoutesWithRedirects).toContain(r)); expect(allRoutes.size).toEqual(fixtureRoutesWithRedirects.size); const redirect = routes.filter(route => route.path === '').pop(); expect(redirect?.redirectTo).toEqual('bar'); expect(redirect?.lazy).toEqual(false); }); it('should produce routes with proper paths', () => { const routes = parseRoutes( 'packages/guess-parser/test/fixtures/angular/src/tsconfig.app.json' ); const route = routes.find(r => r.path === '/foo'); expect(route!.modulePath.endsWith('foo.module.ts')).toBeTruthy(); expect(route!.lazy).toBeTruthy(); expect(route!.parentModulePath!.endsWith('app.module.ts')).toBeTruthy(); }); it('should work with nx monorepo with path mappings', () => { const routes = parseRoutes( 'packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/tsconfig.app.json' ).map(r => r.path); [...routes].forEach(r => expect(nxRoutes).toContain(r)); }); it('should discover redirects', () => { const routes = parseRoutes( 'packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/tsconfig.app.json' , undefined, { redirects: true }).map(r => r.path); [...routes].forEach(r => expect(nxRoutesWithRedirects).toContain(r)); }); }); ================================================ FILE: packages/guess-parser/test/detect.spec.ts ================================================ import { detect } from '../src/detector'; import { ProjectType } from '../../common/interfaces'; describe('detect', () => { describe('angular', () => { it('should detect an Angular app', () => { expect(detect('packages/guess-parser/test/fixtures/angular')!.type).toBe(ProjectType.AngularCLI); }); it('should detect an Angular version 8 app', () => { expect(detect('packages/guess-parser/test/fixtures/ng8')!.type).toBe(ProjectType.AngularCLI); }); }); describe('create-react-app', () => { it('should detect an create-react-app', () => { expect(detect('packages/guess-parser/test/fixtures/react-app')!.type).toBe(ProjectType.CreateReactApp); }); }); describe('create-react-app-ts', () => { it('should detect an create-react-app-ts', () => { expect(detect('packages/guess-parser/test/fixtures/react-app-ts')!.type).toBe( ProjectType.CreateReactAppTypeScript ); }); }); describe('gatsby', () => { it('should detect an gatsby', () => { expect(detect('packages/guess-parser/test/fixtures/gatsby')!.type).toBe(ProjectType.Gatsby); }); }); describe('unknown', () => { it('should not detect unknown app', () => { expect(detect('packages/guess-parser/test/fixtures/unknown')).toBe(undefined); }); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/angular/.angular-cli.json ================================================ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "project": { "name": "angular" }, "apps": [ { "root": "src", "outDir": "dist", "assets": [ "assets", "favicon.ico" ], "index": "index.html", "main": "main.ts", "polyfills": "polyfills.ts", "test": "test.ts", "tsconfig": "tsconfig.app.json", "testTsconfig": "tsconfig.spec.json", "prefix": "app", "styles": [ "styles.css" ], "scripts": [], "environmentSource": "environments/environment.ts", "environments": { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts" } } ], "e2e": { "protractor": { "config": "./protractor.conf.js" } }, "lint": [ { "project": "src/tsconfig.app.json", "exclude": "**/node_modules/**" }, { "project": "src/tsconfig.spec.json", "exclude": "**/node_modules/**" }, { "project": "e2e/tsconfig.e2e.json", "exclude": "**/node_modules/**" } ], "test": { "karma": { "config": "./karma.conf.js" } }, "defaults": { "styleExt": "css", "component": {} } } ================================================ FILE: packages/guess-parser/test/fixtures/angular/library/index.ts ================================================ export * from './nested/library.module'; ================================================ FILE: packages/guess-parser/test/fixtures/angular/library/library.module.ts ================================================ import { NgModule, Component } from '@angular/core'; import { RouterModule } from '@angular/router'; @Component({ }) class LibraryComponent {} @NgModule({ declarations: [LibraryComponent], imports: [RouterModule.forChild([ { path: '', component: LibraryComponent, pathMatch: 'full' } ])], bootstrap: [LibraryComponent] }) export class LibraryModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/library/nested/library.module.ts ================================================ import { NgModule, Component } from '@angular/core'; import { RouterModule } from '@angular/router'; @Component({ }) class LibraryComponent {} @NgModule({ declarations: [LibraryComponent], imports: [RouterModule.forChild([ { path: '', component: LibraryComponent, pathMatch: 'full' } ])], bootstrap: [LibraryComponent] }) export class LibraryModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/library/tsconfig.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/library", "module": "es2015", "types": [] }, "include": ["**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/angular/package.json ================================================ { "name": "angular", "version": "0.0.0", "license": "MIT", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build --prod", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" }, "private": true, "dependencies": { "@angular/animations": "^5.2.0", "@angular/common": "^5.2.0", "@angular/compiler": "^5.2.0", "@angular/core": "^5.2.0", "@angular/forms": "^5.2.0", "@angular/http": "^5.2.0", "@angular/platform-browser": "^5.2.0", "@angular/platform-browser-dynamic": "^5.2.0", "@angular/router": "^5.2.0", "core-js": "^2.4.1", "rxjs": "^5.5.6", "zone.js": "^0.8.19" }, "devDependencies": { "@angular/cli": "~1.7.1", "@angular/compiler-cli": "^5.2.0", "@angular/language-service": "^5.2.0", "@types/jasmine": "~2.8.3", "@types/jasminewd2": "~2.0.2", "@types/node": "~6.0.60", "codelyzer": "^4.0.1", "jasmine-core": "~2.8.0", "jasmine-spec-reporter": "~4.2.1", "karma": "~2.0.0", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "^1.2.1", "karma-jasmine": "~1.1.0", "karma-jasmine-html-reporter": "^0.2.2", "protractor": "~5.1.2", "ts-node": "~4.1.0", "tslint": "~5.9.1", "typescript": "~2.5.3" } } ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/about/about-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = []; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class AboutRoutingModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/about/about.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AboutRoutingModule } from './about-routing.module'; @NgModule({ declarations: [], imports: [ CommonModule, AboutRoutingModule ] }) export class AboutModule { } ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/app-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { BarSimpleComponent } from './bar-simple.component'; const module = './foo/foo.module'; const routes: Routes = [ // { // path: 'foo', // component: BarSimpleComponent, // children: [ // { // path: 'qux', // component: BarSimpleComponent, // children: [ // { // path: 'bar', // loadChildren: () => import('./bar/bar.module').then(m => m.BarModule) // } // ] // } // ] // }, { path: 'fo' + 'o', loadChildren: () => import(module).then(e => e.FooModule) }, { path: 'bar', loadChildren: () => import('./bar/bar.module').then(m => m.BarModule) }, { path: 'qux', loadChildren: 'app/qux/qux.module#QuxModule' }, { path: 'library', loadChildren: 'app/wrapper/wrapper.module#WrapperModule' }, { path: 'bar-simple', component: BarSimpleComponent }, { path: 'about', loadChildren: () => import('./about/about.module').then(m => m.AboutModule) }, { path: 'eager', children: [ { path: 'lazy', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) } ] }, { path: '', pathMatch: 'full', redirectTo: 'bar' } ]; @NgModule({ declarations: [BarSimpleComponent], imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/app.component.css ================================================ ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/app.component.html ================================================ Foo Bar Baz ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/app.component.spec.ts ================================================ import { TestBed, async } from '@angular/core/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ AppComponent ], }).compileComponents(); })); it('should create the app', async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); })); it(`should have as title 'app'`, async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app.title).toEqual('app'); })); it('should render title in a h1 tag', async(() => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); })); }); ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app'; } ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, AppRoutingModule], providers: [], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/bar/bar.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; @NgModule({ imports: [ RouterModule.forChild([ { path: 'baz', loadChildren: () => import('./baz/baz.module').then(m => m.BazModule) } ]) ] }) export class BarModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/bar/baz/baz.module.ts ================================================ import { NgModule, Component } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import './cycle-parent'; @Component({ selector: 'app-baz', template: 'Baz' }) export class BazComponent {} const routes: Routes = [ { path: '', pathMatch: 'full', component: BazComponent } ]; @NgModule({ declarations: [BazComponent], imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class BazModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/bar/baz/cycle-parent.ts ================================================ import './baz.module'; ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/bar-simple.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'app-component', template: 'Bar Simple' }) export class BarSimpleComponent {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/foo/baz/baz-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { BazComponent } from './baz.component'; const routes: Routes = [ { path: '', component: BazComponent }, { path: 'index', component: BazComponent } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class BazRoutingModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/foo/baz/baz.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'app-baz', template: 'baz-component' }) export class BazComponent {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/foo/baz/baz.module.ts ================================================ import { NgModule } from '@angular/core'; import { BazComponent } from './baz.component'; import { BazRoutingModule } from './baz-routing.module'; @NgModule({ declarations: [BazComponent], imports: [BazRoutingModule], bootstrap: [BazComponent] }) export class FooModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/foo/foo-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { FooComponent } from './foo.component'; const baz = 'baz'; const routes: Routes = [ { path: '', pathMatch: 'full', component: FooComponent }, { path: 'index', component: FooComponent }, { path: 'baz', loadChildren: baz + '/baz.module#BazModule' }, { path: '', children: [ { path: 'child1', component: FooComponent } ] }, { path: 'foo-parent', children: [ { path: 'child2', component: FooComponent } ] } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class FooRoutingModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/foo/foo.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'app-foo', template: 'foo-component' }) export class FooComponent {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/foo/foo.module.ts ================================================ import { NgModule, Component } from '@angular/core'; import { FooComponent } from './foo.component'; import { FooRoutingModule } from './foo-routing.module'; @NgModule({ declarations: [FooComponent], imports: [FooRoutingModule], bootstrap: [FooComponent] }) export class FooModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/lazy/lazy-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { LazyComponent } from './lazy.component'; const routes: Routes = [{ path: '', component: LazyComponent }]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class LazyRoutingModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/lazy/lazy.component.ts ================================================ import { Component } from '@angular/core'; @Component({ template: '', }) export class LazyComponent { } ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/lazy/lazy.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { LazyRoutingModule } from './lazy-routing.module'; import { LazyComponent } from './lazy.component'; @NgModule({ declarations: [ LazyComponent ], imports: [ CommonModule, LazyRoutingModule ] }) export class LazyModule { } ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/qux/qux.module.ts ================================================ import { NgModule, Component } from '@angular/core'; import { RouterModule } from '@angular/router'; @Component({ }) class QuxComponent {} @NgModule({ declarations: [QuxComponent], imports: [RouterModule.forChild([ { path: '', component: QuxComponent, pathMatch: 'full' } ])], bootstrap: [QuxComponent] }) export class QuxModule {} ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/app/wrapper/wrapper.module.ts ================================================ import { NgModule } from '@angular/core'; import { LibraryModule } from '~library'; @NgModule({ imports: [LibraryModule] }) export class WrapperModule {} import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/environments/environment.prod.ts ================================================ export const environment = { production: true }; ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/environments/environment.ts ================================================ // The file contents for the current environment will overwrite these during build. // The build system defaults to the dev environment which uses `environment.ts`, but if you do // `ng build --env=prod` then `environment.prod.ts` will be used instead. // The list of which env maps to which file can be found in `.angular-cli.json`. export const environment = { production: false }; ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/index.html ================================================ Angular ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/main.ts ================================================ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.log(err)); ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/polyfills.ts ================================================ /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. * * This file is divided into 2 sections: * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. * 2. Application imports. Files imported after ZoneJS that should be loaded before your main * file. * * The current setup is for so-called "evergreen" browsers; the last versions of browsers that * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html */ /*************************************************************************************************** * BROWSER POLYFILLS */ /** IE9, IE10 and IE11 requires all of the following polyfills. **/ // import 'core-js/es6/symbol'; // import 'core-js/es6/object'; // import 'core-js/es6/function'; // import 'core-js/es6/parse-int'; // import 'core-js/es6/parse-float'; // import 'core-js/es6/number'; // import 'core-js/es6/math'; // import 'core-js/es6/string'; // import 'core-js/es6/date'; // import 'core-js/es6/array'; // import 'core-js/es6/regexp'; // import 'core-js/es6/map'; // import 'core-js/es6/weak-map'; // import 'core-js/es6/set'; /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. /** IE10 and IE11 requires the following for the Reflect API. */ // import 'core-js/es6/reflect'; /** Evergreen browsers require these. **/ // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. import 'core-js/es7/reflect'; /** * Required to support Web Animations `@angular/platform-browser/animations`. * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation **/ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags */ // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames /* * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * with the following flag, it will bypass `zone.js` patch for IE/Edge */ // (window as any).__Zone_enable_cross_context_check = true; /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/styles.css ================================================ /* You can add global styles to this file, and also import other style files */ ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/tsconfig.app.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", "module": "es2015", "types": [] }, "exclude": [ "test.ts", "**/*.spec.ts" ] } ================================================ FILE: packages/guess-parser/test/fixtures/angular/src/tsconfig.spec.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/spec", "module": "commonjs", "types": [ "jasmine", "node" ] }, "files": [ "test.ts" ], "include": [ "**/*.spec.ts", "**/*.d.ts" ] } ================================================ FILE: packages/guess-parser/test/fixtures/angular/tsconfig.json ================================================ { "compileOnSave": false, "compilerOptions": { "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es5", "typeRoots": [ "node_modules/@types" ], "lib": [ "es2017", "dom" ], "baseUrl": ".", "paths": { "~library": ["library/index.ts"], } } } ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/.gitignore ================================================ # Project dependencies .cache node_modules yarn-error.log # Build directory /public .DS_Store ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/.prettierrc ================================================ { "semi": false, "singleQuote": true, "trailingComma": "es5" } ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 gatsbyjs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/README.md ================================================ # gatsby-starter-default The default Gatsby starter. For an overview of the project structure please refer to the [Gatsby documentation - Building with Components](https://www.gatsbyjs.org/docs/building-with-components/). ## Install Make sure that you have the Gatsby CLI program installed: ```sh npm install --global gatsby-cli ``` And run from your CLI: ```sh gatsby new gatsby-example-site ``` Then you can run it by: ```sh cd gatsby-example-site npm run develop ``` ## Deploy [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/gatsbyjs/gatsby-starter-default) ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/gatsby-browser.js ================================================ /** * Implement Gatsby's Browser APIs in this file. * * See: https://www.gatsbyjs.org/docs/browser-apis/ */ // You can delete this file if you're not using it ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/gatsby-config.js ================================================ module.exports = { siteMetadata: { title: 'Gatsby Default Starter', }, plugins: ['gatsby-plugin-react-helmet'], } ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/gatsby-node.js ================================================ /** * Implement Gatsby's Node APIs in this file. * * See: https://www.gatsbyjs.org/docs/node-apis/ */ // You can delete this file if you're not using it ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/gatsby-ssr.js ================================================ /** * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. * * See: https://www.gatsbyjs.org/docs/ssr-apis/ */ // You can delete this file if you're not using it ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/package.json ================================================ { "name": "gatsby-starter-default", "description": "Gatsby default starter", "version": "1.0.0", "author": "Kyle Mathews ", "dependencies": { "gatsby": "^1.9.247", "gatsby-link": "^1.6.40", "gatsby-plugin-react-helmet": "^2.0.10", "react-helmet": "^5.2.0" }, "keywords": [ "gatsby" ], "license": "MIT", "scripts": { "build": "gatsby build", "develop": "gatsby develop", "format": "prettier --write 'src/**/*.js'", "test": "echo \"Error: no test specified\" && exit 1" }, "devDependencies": { "prettier": "^1.12.0" } } ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/src/components/header.js ================================================ import React from 'react' import Link from 'gatsby-link' const Header = ({ siteTitle }) => (

{siteTitle}

) export default Header ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/src/layouts/index.css ================================================ html { font-family: sans-serif; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; } body { margin: 0; } article, aside, details, figcaption, figure, footer, header, main, menu, nav, section, summary { display: block; } audio, canvas, progress, video { display: inline-block; } audio:not([controls]) { display: none; height: 0; } progress { vertical-align: baseline; } [hidden], template { display: none; } a { background-color: transparent; -webkit-text-decoration-skip: objects; } a:active, a:hover { outline-width: 0; } abbr[title] { border-bottom: none; text-decoration: underline; text-decoration: underline dotted; } b, strong { font-weight: inherit; font-weight: bolder; } dfn { font-style: italic; } h1 { font-size: 2em; margin: .67em 0; } mark { background-color: #ff0; color: #000; } small { font-size: 80%; } sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sub { bottom: -.25em; } sup { top: -.5em; } img { border-style: none; } svg:not(:root) { overflow: hidden; } code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } figure { margin: 1em 40px; } hr { box-sizing: content-box; height: 0; overflow: visible; } button, input, optgroup, select, textarea { font: inherit; margin: 0; } optgroup { font-weight: 700; } button, input { overflow: visible; } button, select { text-transform: none; } [type=reset], [type=submit], button, html [type=button] { -webkit-appearance: button; } [type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner { border-style: none; padding: 0; } [type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring { outline: 1px dotted ButtonText; } fieldset { border: 1px solid silver; margin: 0 2px; padding: .35em .625em .75em; } legend { box-sizing: border-box; color: inherit; display: table; max-width: 100%; padding: 0; white-space: normal; } textarea { overflow: auto; } [type=checkbox], [type=radio] { box-sizing: border-box; padding: 0; } [type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button { height: auto; } [type=search] { -webkit-appearance: textfield; outline-offset: -2px; } [type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration { -webkit-appearance: none; } ::-webkit-input-placeholder { color: inherit; opacity: .54; } ::-webkit-file-upload-button { -webkit-appearance: button; font: inherit; } html { font: 112.5%/1.45em georgia, serif; box-sizing: border-box; overflow-y: scroll; } * { box-sizing: inherit; } *:before { box-sizing: inherit; } *:after { box-sizing: inherit; } body { color: hsla(0, 0%, 0%, 0.8); font-family: georgia, serif; font-weight: normal; word-wrap: break-word; font-kerning: normal; -moz-font-feature-settings: "kern", "liga", "clig", "calt"; -ms-font-feature-settings: "kern", "liga", "clig", "calt"; -webkit-font-feature-settings: "kern", "liga", "clig", "calt"; font-feature-settings: "kern", "liga", "clig", "calt"; } img { max-width: 100%; margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } h1 { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; color: inherit; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-weight: bold; text-rendering: optimizeLegibility; font-size: 2.25rem; line-height: 1.1; } h2 { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; color: inherit; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-weight: bold; text-rendering: optimizeLegibility; font-size: 1.62671rem; line-height: 1.1; } h3 { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; color: inherit; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-weight: bold; text-rendering: optimizeLegibility; font-size: 1.38316rem; line-height: 1.1; } h4 { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; color: inherit; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-weight: bold; text-rendering: optimizeLegibility; font-size: 1rem; line-height: 1.1; } h5 { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; color: inherit; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-weight: bold; text-rendering: optimizeLegibility; font-size: 0.85028rem; line-height: 1.1; } h6 { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; color: inherit; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-weight: bold; text-rendering: optimizeLegibility; font-size: 0.78405rem; line-height: 1.1; } hgroup { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } ul { margin-left: 1.45rem; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; list-style-position: outside; list-style-image: none; } ol { margin-left: 1.45rem; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; list-style-position: outside; list-style-image: none; } dl { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } dd { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } p { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } figure { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } pre { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; font-size: 0.85rem; line-height: 1.42; background: hsla(0, 0%, 0%, 0.04); border-radius: 3px; overflow: auto; word-wrap: normal; padding: 1.45rem; } table { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; font-size: 1rem; line-height: 1.45rem; border-collapse: collapse; width: 100%; } fieldset { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } blockquote { margin-left: 1.45rem; margin-right: 1.45rem; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } form { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } noscript { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } iframe { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } hr { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: calc(1.45rem - 1px); background: hsla(0, 0%, 0%, 0.2); border: none; height: 1px; } address { margin-left: 0; margin-right: 0; margin-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; margin-bottom: 1.45rem; } b { font-weight: bold; } strong { font-weight: bold; } dt { font-weight: bold; } th { font-weight: bold; } li { margin-bottom: calc(1.45rem / 2); } ol li { padding-left: 0; } ul li { padding-left: 0; } li > ol { margin-left: 1.45rem; margin-bottom: calc(1.45rem / 2); margin-top: calc(1.45rem / 2); } li > ul { margin-left: 1.45rem; margin-bottom: calc(1.45rem / 2); margin-top: calc(1.45rem / 2); } blockquote *:last-child { margin-bottom: 0; } li *:last-child { margin-bottom: 0; } p *:last-child { margin-bottom: 0; } li > p { margin-bottom: calc(1.45rem / 2); } code { font-size: 0.85rem; line-height: 1.45rem; } kbd { font-size: 0.85rem; line-height: 1.45rem; } samp { font-size: 0.85rem; line-height: 1.45rem; } abbr { border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); cursor: help; } acronym { border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); cursor: help; } abbr[title] { border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); cursor: help; text-decoration: none; } thead { text-align: left; } td, th { text-align: left; border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); font-feature-settings: "tnum"; -moz-font-feature-settings: "tnum"; -ms-font-feature-settings: "tnum"; -webkit-font-feature-settings: "tnum"; padding-left: 0.96667rem; padding-right: 0.96667rem; padding-top: 0.725rem; padding-bottom: calc(0.725rem - 1px); } th:first-child, td:first-child { padding-left: 0; } th:last-child, td:last-child { padding-right: 0; } tt, code { background-color: hsla(0, 0%, 0%, 0.04); border-radius: 3px; font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono", "Liberation Mono", Menlo, Courier, monospace; padding: 0; padding-top: 0.2em; padding-bottom: 0.2em; } pre code { background: none; line-height: 1.42; } code:before, code:after, tt:before, tt:after { letter-spacing: -0.2em; content: " "; } pre code:before, pre code:after, pre tt:before, pre tt:after { content: ""; } @media only screen and (max-width: 480px) { html { font-size: 100%; } } ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/src/layouts/index.js ================================================ import React from 'react' import PropTypes from 'prop-types' import Helmet from 'react-helmet' import Header from '../components/header' import './index.css' const Layout = ({ children, data }) => (
{children()}
) Layout.propTypes = { children: PropTypes.func, } export default Layout export const query = graphql` query SiteTitleQuery { site { siteMetadata { title } } } ` ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/src/pages/404.js ================================================ import React from 'react' const NotFoundPage = () => (

NOT FOUND

You just hit a route that doesn't exist... the sadness.

) export default NotFoundPage ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/src/pages/index.js ================================================ import React from 'react' import Link from 'gatsby-link' const IndexPage = () => (

Hi people

Welcome to your new Gatsby site.

Now go build something great.

Go to page 2
) export default IndexPage ================================================ FILE: packages/guess-parser/test/fixtures/gatsby/src/pages/page-2.js ================================================ import React from 'react' import Link from 'gatsby-link' const SecondPage = () => (

Hi from the second page

Welcome to page 2

Go back to the homepage
) export default SecondPage ================================================ FILE: packages/guess-parser/test/fixtures/ng8/.editorconfig ================================================ # Editor configuration, see https://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true [*.md] max_line_length = off trim_trailing_whitespace = false ================================================ FILE: packages/guess-parser/test/fixtures/ng8/.gitignore ================================================ # See http://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist /tmp /out-tsc # Only exists if Bazel was run /bazel-out # dependencies /node_modules # profiling files chrome-profiler-events.json speed-measure-plugin.json # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .history/* # misc /.sass-cache /connect.lock /coverage /libpeerconnection.log npm-debug.log yarn-error.log testem.log /typings # System Files .DS_Store Thumbs.db ================================================ FILE: packages/guess-parser/test/fixtures/ng8/README.md ================================================ # Ng8 This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.0.1. ## Development server Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. ## Code scaffolding Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. ## Build Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. ## Running unit tests Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). ## Running end-to-end tests Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). ## Further help To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). ================================================ FILE: packages/guess-parser/test/fixtures/ng8/angular.json ================================================ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "ng8": { "projectType": "application", "schematics": {}, "root": "", "sourceRoot": "src", "prefix": "app", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/ng8", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ "src/styles.css" ], "scripts": [] }, "configurations": { "production": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "budgets": [ { "type": "initial", "maximumWarning": "2mb", "maximumError": "5mb" } ] } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "ng8:build" }, "configurations": { "production": { "browserTarget": "ng8:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "ng8:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ "src/styles.css" ], "scripts": [] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "tsconfig.app.json", "tsconfig.spec.json", "e2e/tsconfig.json" ], "exclude": [ "**/node_modules/**" ] } }, "e2e": { "builder": "@angular-devkit/build-angular:protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "ng8:serve" }, "configurations": { "production": { "devServerTarget": "ng8:serve:production" } } } } }}, "defaultProject": "ng8" } ================================================ FILE: packages/guess-parser/test/fixtures/ng8/browserslist ================================================ # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries # You can see what browsers were selected by your queries by running: # npx browserslist > 0.5% last 2 versions Firefox ESR not dead not IE 9-11 # For IE 9-11 support, remove 'not'. ================================================ FILE: packages/guess-parser/test/fixtures/ng8/e2e/protractor.conf.js ================================================ // @ts-check // Protractor configuration file, see link for more information // https://github.com/angular/protractor/blob/master/lib/config.ts const { SpecReporter } = require('jasmine-spec-reporter'); /** * @type { import("protractor").Config } */ exports.config = { allScriptsTimeout: 11000, specs: [ './src/**/*.e2e-spec.ts' ], capabilities: { 'browserName': 'chrome' }, directConnect: true, baseUrl: 'http://localhost:4200/', framework: 'jasmine', jasmineNodeOpts: { showColors: true, defaultTimeoutInterval: 30000, print: function() {} }, onPrepare() { require('ts-node').register({ project: require('path').join(__dirname, './tsconfig.json') }); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); } }; ================================================ FILE: packages/guess-parser/test/fixtures/ng8/e2e/src/app.e2e-spec.ts ================================================ import { AppPage } from './app.po'; import { browser, logging } from 'protractor'; describe('workspace-project App', () => { let page: AppPage; beforeEach(() => { page = new AppPage(); }); it('should display welcome message', () => { page.navigateTo(); expect(page.getTitleText()).toEqual('Welcome to ng8!'); }); afterEach(async () => { // Assert that there are no errors emitted from the browser const logs = await browser.manage().logs().get(logging.Type.BROWSER); expect(logs).not.toContain(jasmine.objectContaining({ level: logging.Level.SEVERE, } as logging.Entry)); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/ng8/e2e/src/app.po.ts ================================================ import { browser, by, element } from 'protractor'; export class AppPage { navigateTo() { return browser.get(browser.baseUrl) as Promise; } getTitleText() { return element(by.css('app-root h1')).getText() as Promise; } } ================================================ FILE: packages/guess-parser/test/fixtures/ng8/e2e/tsconfig.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/e2e", "module": "commonjs", "target": "es5", "types": [ "jasmine", "jasminewd2", "node" ] } } ================================================ FILE: packages/guess-parser/test/fixtures/ng8/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client: { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, './coverage/ng8'), reports: ['html', 'lcovonly', 'text-summary'], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false, restartOnFileChange: true }); }; ================================================ FILE: packages/guess-parser/test/fixtures/ng8/package.json ================================================ { "name": "ng8", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" }, "private": true, "dependencies": { "@angular/animations": "~8.0.0", "@angular/common": "~8.0.0", "@angular/compiler": "~8.0.0", "@angular/core": "~8.0.0", "@angular/forms": "~8.0.0", "@angular/platform-browser": "~8.0.0", "@angular/platform-browser-dynamic": "~8.0.0", "@angular/router": "~8.0.0", "rxjs": "~6.4.0", "tslib": "^1.9.0", "zone.js": "~0.9.1" }, "devDependencies": { "@angular-devkit/build-angular": "~0.800.0", "@angular/cli": "~8.0.1", "@angular/compiler-cli": "~8.0.0", "@angular/language-service": "~8.0.0", "@types/node": "~8.9.4", "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", "codelyzer": "^5.0.0", "jasmine-core": "~3.4.0", "jasmine-spec-reporter": "~4.2.1", "karma": "~4.1.0", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "~2.0.1", "karma-jasmine": "~2.0.1", "karma-jasmine-html-reporter": "^1.4.0", "protractor": "~5.4.0", "ts-node": "~7.0.0", "tslint": "~5.15.0", "typescript": "~3.4.3" } } ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/app/app-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = []; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/app/app.component.css ================================================ ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/app/app.component.html ================================================

Welcome to {{ title }}!

Angular Logo

Here are some links to help you start:

================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/app/app.component.spec.ts ================================================ import { TestBed, async } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ RouterTestingModule ], declarations: [ AppComponent ], }).compileComponents(); })); it('should create the app', () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'ng8'`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app.title).toEqual('ng8'); }); it('should render title in a h1 tag', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain('Welcome to ng8!'); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/app/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'ng8'; } ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/app/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/assets/.gitkeep ================================================ ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/environments/environment.prod.ts ================================================ export const environment = { production: true }; ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/environments/environment.ts ================================================ // This file can be replaced during build by using the `fileReplacements` array. // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. export const environment = { production: false }; /* * For easier debugging in development mode, you can import the following file * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. * * This import should be commented out in production mode because it will have a negative impact * on performance if an error is thrown. */ // import 'zone.js/dist/zone-error'; // Included with Angular CLI. ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/index.html ================================================ Ng8 ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/main.ts ================================================ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.error(err)); ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/polyfills.ts ================================================ /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. * * This file is divided into 2 sections: * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. * 2. Application imports. Files imported after ZoneJS that should be loaded before your main * file. * * The current setup is for so-called "evergreen" browsers; the last versions of browsers that * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** * BROWSER POLYFILLS */ /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. /** * Web Animations `@angular/platform-browser/animations` * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). */ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags * because those flags need to be set before `zone.js` being loaded, and webpack * will put import in the top of bundle, so user need to create a separate file * in this directory (for example: zone-flags.ts), and put the following flags * into that file, and then add the following code before importing zone.js. * import './zone-flags.ts'; * * The flags allowed in zone-flags.ts are listed here. * * The following flags will work for all browsers. * * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames * * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * with the following flag, it will bypass `zone.js` patch for IE/Edge * * (window as any).__Zone_enable_cross_context_check = true; * */ /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/styles.css ================================================ /* You can add global styles to this file, and also import other style files */ ================================================ FILE: packages/guess-parser/test/fixtures/ng8/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: packages/guess-parser/test/fixtures/ng8/tsconfig.app.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] }, "include": [ "src/**/*.ts" ], "exclude": [ "src/test.ts", "src/**/*.spec.ts" ] } ================================================ FILE: packages/guess-parser/test/fixtures/ng8/tsconfig.json ================================================ { "compileOnSave": false, "compilerOptions": { "baseUrl": "./", "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, "module": "esnext", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "target": "es2015", "typeRoots": [ "node_modules/@types" ], "lib": [ "es2018", "dom" ] } } ================================================ FILE: packages/guess-parser/test/fixtures/ng8/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/spec", "types": [ "jasmine", "node" ] }, "files": [ "src/test.ts", "src/polyfills.ts" ], "include": [ "src/**/*.spec.ts", "src/**/*.d.ts" ] } ================================================ FILE: packages/guess-parser/test/fixtures/ng8/tslint.json ================================================ { "extends": "tslint:recommended", "rules": { "array-type": false, "arrow-parens": false, "deprecation": { "severity": "warn" }, "component-class-suffix": true, "contextual-lifecycle": true, "directive-class-suffix": true, "directive-selector": [ true, "attribute", "app", "camelCase" ], "component-selector": [ true, "element", "app", "kebab-case" ], "import-blacklist": [ true, "rxjs/Rx" ], "interface-name": false, "max-classes-per-file": false, "max-line-length": [ true, 140 ], "member-access": false, "member-ordering": [ true, { "order": [ "static-field", "instance-field", "static-method", "instance-method" ] } ], "no-consecutive-blank-lines": false, "no-console": [ true, "debug", "info", "time", "timeEnd", "trace" ], "no-empty": false, "no-inferrable-types": [ true, "ignore-params" ], "no-non-null-assertion": true, "no-redundant-jsdoc": true, "no-switch-case-fall-through": true, "no-use-before-declare": true, "no-var-requires": false, "object-literal-key-quotes": [ true, "as-needed" ], "object-literal-sort-keys": false, "ordered-imports": false, "quotemark": [ true, "single" ], "trailing-comma": false, "no-conflicting-lifecycle": true, "no-host-metadata-property": true, "no-input-rename": true, "no-inputs-metadata-property": true, "no-output-native": true, "no-output-on-prefix": true, "no-output-rename": true, "no-outputs-metadata-property": true, "template-banana-in-box": true, "template-no-negated-async": true, "use-lifecycle-interface": true, "use-pipe-transform-interface": true }, "rulesDirectory": [ "codelyzer" ] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/LICENSE ================================================ MIT License Copyright (c) 2019 Christian Janz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/guess-parser/test/fixtures/nx/README.md ================================================ # Angular monorepo example using Nx This app shows the process of refactoring an Angular app to a monorepo with the help of [Nx](https://nx.dev/angular) ================================================ FILE: packages/guess-parser/test/fixtures/nx/angular.json ================================================ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "", "projects": { "ng-cli-app": { "projectType": "application", "schematics": { "@schematics/angular:component": { "style": "scss" } }, "root": "apps/ng-cli-app", "sourceRoot": "apps/ng-cli-app/src", "prefix": "app", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/apps/ng-cli-app", "index": "apps/ng-cli-app/src/index.html", "main": "apps/ng-cli-app/src/main.ts", "polyfills": "apps/ng-cli-app/src/polyfills.ts", "tsConfig": "apps/ng-cli-app/tsconfig.app.json", "aot": false, "assets": [ "apps/ng-cli-app/src/favicon.ico", "apps/ng-cli-app/src/assets" ], "styles": [ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "apps/ng-cli-app/src/styles.scss" ], "scripts": [] }, "configurations": { "production": { "fileReplacements": [ { "replace": "apps/ng-cli-app/src/environments/environment.ts", "with": "apps/ng-cli-app/src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "budgets": [ { "type": "initial", "maximumWarning": "2mb", "maximumError": "5mb" }, { "type": "anyComponentStyle", "maximumWarning": "6kb", "maximumError": "10kb" } ] } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "ng-cli-app:build" }, "configurations": { "production": { "browserTarget": "ng-cli-app:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "ng-cli-app:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "apps/ng-cli-app/src/test.ts", "polyfills": "apps/ng-cli-app/src/polyfills.ts", "tsConfig": "apps/ng-cli-app/tsconfig.spec.json", "karmaConfig": "apps/ng-cli-app/karma.conf.js", "assets": [ "apps/ng-cli-app/src/favicon.ico", "apps/ng-cli-app/src/assets" ], "styles": [ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "apps/ng-cli-app/src/styles.scss" ], "scripts": [] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "apps/ng-cli-app/tsconfig.app.json", "apps/ng-cli-app/tsconfig.spec.json" ], "exclude": [ "**/node_modules/**" ] } } } }, "ng-cli-app-e2e": { "projectType": "application", "root": "apps/ng-cli-app-e2e", "architect": { "e2e": { "builder": "@nrwl/cypress:cypress", "options": { "cypressConfig": "apps/ng-cli-app-e2e/cypress.json", "devServerTarget": "ng-cli-app:serve", "tsConfig": "apps/ng-cli-app-e2e/tsconfig.e2e.json" }, "configurations": { "production": { "devServerTarget": "ng-cli-app:serve:production" } } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": "apps/ng-cli-app-e2e/tsconfig.json", "exclude": [ "**/node_modules/**" ] } } } }, "shared-components": { "projectType": "library", "root": "libs/shared/components", "sourceRoot": "libs/shared/components/src", "prefix": "ng-cli-app", "architect": { "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/shared/components/tsconfig.lib.json", "libs/shared/components/tsconfig.spec.json" ], "exclude": [ "**/node_modules/**", "!libs/shared/components/**" ] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "libs/shared/components/jest.config.js", "tsConfig": "libs/shared/components/tsconfig.spec.json", "setupFile": "libs/shared/components/src/test-setup.ts" } }, "storybook": { "builder": "@nrwl/storybook:storybook", "options": { "uiFramework": "@storybook/angular", "port": 4400, "config": { "configFolder": "libs/shared/components/.storybook" } }, "configurations": { "ci": { "quiet": true } } } }, "schematics": { "@nrwl/angular:component": { "styleext": "scss" } } }, "auth": { "projectType": "library", "root": "libs/auth", "sourceRoot": "libs/auth/src", "prefix": "app", "architect": { "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/auth/tsconfig.lib.json", "libs/auth/tsconfig.spec.json" ], "exclude": [ "**/node_modules/**", "!libs/auth/**" ] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "libs/auth/jest.config.js", "tsConfig": "libs/auth/tsconfig.spec.json", "setupFile": "libs/auth/src/test-setup.ts" } } }, "schematics": { "@nrwl/angular:component": { "styleext": "scss" } } }, "feat-customers": { "projectType": "library", "root": "libs/customers/ui", "sourceRoot": "libs/customers/ui/src", "prefix": "app", "architect": { "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/customers/ui/tsconfig.lib.json", "libs/customers/ui/tsconfig.spec.json" ], "exclude": [ "**/node_modules/**", "!libs/customers/ui/**" ] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "libs/customers/ui/jest.config.js", "tsConfig": "libs/customers/ui/tsconfig.spec.json", "setupFile": "libs/customers/ui/src/test-setup.ts" } } }, "schematics": { "@nrwl/angular:component": { "styleext": "scss" } } }, "customers-data": { "projectType": "library", "root": "libs/customers/data", "sourceRoot": "libs/customers/data/src", "prefix": "app", "architect": { "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/customers/data/tsconfig.lib.json", "libs/customers/data/tsconfig.spec.json" ], "exclude": [ "**/node_modules/**", "!libs/customers/data/**" ] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "libs/customers/data/jest.config.js", "tsConfig": "libs/customers/data/tsconfig.spec.json", "setupFile": "libs/customers/data/src/test-setup.ts" } } }, "schematics": { "@nrwl/angular:component": { "styleext": "scss" } } }, "feat-home": { "projectType": "library", "root": "libs/home/ui", "sourceRoot": "libs/home/ui/src", "prefix": "app", "architect": { "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/home/ui/tsconfig.lib.json", "libs/home/ui/tsconfig.spec.json" ], "exclude": [ "**/node_modules/**", "!libs/home/ui/**" ] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "libs/home/ui/jest.config.js", "tsConfig": "libs/home/ui/tsconfig.spec.json", "setupFile": "libs/home/ui/src/test-setup.ts" } }, "storybook": { "builder": "@nrwl/storybook:storybook", "options": { "uiFramework": "@storybook/angular", "port": 4400, "config": { "configFolder": "libs/home/ui/.storybook" } }, "configurations": { "ci": { "quiet": true } } } }, "schematics": { "@nrwl/angular:component": { "styleext": "scss" } } }, "shared-components-e2e": { "root": "apps/shared-components-e2e", "sourceRoot": "apps/shared-components-e2e/src", "projectType": "application", "architect": { "e2e": { "builder": "@nrwl/cypress:cypress", "options": { "cypressConfig": "apps/shared-components-e2e/cypress.json", "tsConfig": "apps/shared-components-e2e/tsconfig.e2e.json", "devServerTarget": "shared-components:storybook" }, "configurations": { "production": { "devServerTarget": "shared-components:serve:production" } } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "apps/shared-components-e2e/tsconfig.e2e.json" ], "exclude": [ "**/node_modules/**", "!apps/shared-components-e2e/**" ] } } } }, "feat-home-e2e": { "root": "apps/feat-home-e2e", "sourceRoot": "apps/feat-home-e2e/src", "projectType": "application", "architect": { "e2e": { "builder": "@nrwl/cypress:cypress", "options": { "cypressConfig": "apps/feat-home-e2e/cypress.json", "tsConfig": "apps/feat-home-e2e/tsconfig.e2e.json", "devServerTarget": "feat-home:storybook" }, "configurations": { "production": { "devServerTarget": "feat-home:serve:production" } } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "apps/feat-home-e2e/tsconfig.e2e.json" ], "exclude": [ "**/node_modules/**", "!apps/feat-home-e2e/**" ] } } } } }, "defaultProject": "ng-cli-app" } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/customers-ui-e2e/src/integration/customers.component.spec.ts ================================================ describe('feat-customers', () => { beforeEach(() => cy.visit('/iframe.html?id=customerscomponent--primary')); it('should display correctly', () => { cy.wait(30000); cy.findByText('Customer Data'); cy.findByRole('grid') .get('tbody tr:first-child') .should('contain', '1') .should('contain', 'Waulker'); }); it('should sort by first name', () => { cy.findByText('first_name').click(); cy.findByRole('grid') .get('tbody tr:first-child') .should('contain', '22') .should('contain', 'Scattergood'); cy.findByText('first_name').click(); cy.findByRole('grid') .get('tbody tr:first-child') .should('contain', '25') .should('contain', 'Getley'); cy.findByText('first_name').click(); cy.findByRole('grid') .get('tbody tr:first-child') .should('contain', '1') .should('contain', 'Waulker'); }); it('should render the component', () => { cy.get('app-customers').should('exist'); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/feat-home-e2e/cypress.json ================================================ { "fileServerFolder": ".", "fixturesFolder": "./src/fixtures", "integrationFolder": "./src/integration", "modifyObstructiveCode": false, "pluginsFile": "./src/plugins/index", "supportFile": "./src/support/index.ts", "video": true, "videosFolder": "../../dist/cypress/apps/feat-home-e2e/videos", "screenshotsFolder": "../../dist/cypress/apps/feat-home-e2e/screenshots", "chromeWebSecurity": false, "baseUrl": "http://localhost:4400" } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/feat-home-e2e/src/fixtures/example.json ================================================ { "name": "Using fixtures to represent data", "email": "hello@cypress.io" } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/feat-home-e2e/src/integration/home.component.spec.ts ================================================ describe('feat-home', () => { beforeEach(() => cy.visit('/iframe.html?id=homecomponent--primary')); it('should display correctly', () => { cy.wait(30000); cy.findByText('Welcome to the Demo App'); cy.findByText('🥳 Customer of the day'); cy.contains('mat-icon', 'person'); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/feat-home-e2e/src/plugins/index.js ================================================ // *********************************************************** // This example plugins/index.js can be used to load plugins // // You can change the location of this file or turn off loading // the plugins file with the 'pluginsFile' configuration option. // // You can read more here: // https://on.cypress.io/plugins-guide // *********************************************************** // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config // Preprocess Typescript on('file:preprocessor', preprocessTypescript(config)); }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/feat-home-e2e/src/support/commands.ts ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** // eslint-disable-next-line @typescript-eslint/no-namespace import '@testing-library/cypress/add-commands'; declare namespace Cypress { interface Chainable { login(email: string, password: string): void; } } // // -- This is a parent command -- Cypress.Commands.add('login', (email, password) => { console.log('Custom command example: Login', email, password); }); // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/feat-home-e2e/src/support/index.ts ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/feat-home-e2e/tsconfig.e2e.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "sourceMap": false, "outDir": "../../dist/out-tsc" }, "include": ["src/**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/feat-home-e2e/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["cypress", "node"] }, "include": ["**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/feat-home-e2e/tslint.json ================================================ {"extends":"../../tslint.json","rules":[]} ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/browserslist ================================================ # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries # You can see what browsers were selected by your queries by running: # npx browserslist > 0.5% last 2 versions Firefox ESR not dead not IE 9-11 # For IE 9-11 support, remove 'not'. ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function(config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client: { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, './coverage/ng-cli-app'), reports: ['html', 'lcovonly', 'text-summary'], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: true, restartOnFileChange: true }); }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/app/app.component.html ================================================
{{ user.fullName }}
================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/app/app.component.scss ================================================ ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/app/app.component.spec.ts ================================================ import { TestBed, async } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { AppComponent } from './app.component'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { SharedComponentsModule } from '@ng-cli-app/shared/components'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ RouterTestingModule, NoopAnimationsModule, SharedComponentsModule ], declarations: [AppComponent] }).compileComponents(); })); it('should create the app', () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/app/app.component.ts ================================================ import { IdleMonitorService } from '@scullyio/ng-lib'; import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { AuthService, User } from '@ng-cli-app/auth'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { public user$: Observable; constructor( private idle: IdleMonitorService, private authService: AuthService ) {} ngOnInit(): void { this.user$ = this.authService.currentUser$; } public logout() { this.authService.logout(); } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/app/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { HttpClientModule } from '@angular/common/http'; import { SharedComponentsModule } from '@ng-cli-app/shared/components'; import { MatButtonModule } from '@angular/material/button'; import { AuthModule, AUTH_ROUTES, AuthGuard } from '@ng-cli-app/auth'; import { Routes, RouterModule } from '@angular/router'; export const routes: Routes = [ { path: '', pathMatch: 'full', redirectTo: '/home' }, { path: 'auth', children: AUTH_ROUTES }, { path: 'home', loadChildren: () => import('@ng-cli-app/home/ui').then(m => m.HomeUiModule), canActivate: [AuthGuard] }, { path: 'customers', loadChildren: () => import('@ng-cli-app/customers/ui').then(m => m.CustomersUiModule), canActivate: [AuthGuard] } ]; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, HttpClientModule, RouterModule.forRoot(routes), BrowserAnimationsModule, SharedComponentsModule, MatButtonModule, AuthModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/assets/.gitkeep ================================================ ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/assets/customers.json ================================================ [ { "id": 1, "first_name": "Leigha", "last_name": "Waulker", "email": "lwaulker0@disqus.com", "street": "73587 Dunning Hill", "city": "Newark", "country": "United States" }, { "id": 2, "first_name": "Wilek", "last_name": "Warke", "email": "wwarke1@canalblog.com", "street": "89992 Blaine Drive", "city": "Fresno", "country": "United States" }, { "id": 3, "first_name": "Malory", "last_name": "Gobolos", "email": "mgobolos2@devhub.com", "street": "786 Westport Place", "city": "Hamburg Winterhude", "country": "Germany" }, { "id": 4, "first_name": "Idette", "last_name": "Ferris", "email": "iferris3@imgur.com", "street": "24859 Bobwhite Circle", "city": "Honolulu", "country": "United States" }, { "id": 5, "first_name": "Tierney", "last_name": "Baude", "email": "tbaude4@biblegateway.com", "street": "6738 Golden Leaf Avenue", "city": "Charlotte", "country": "United States" }, { "id": 6, "first_name": "Philipa", "last_name": "Wimsett", "email": "pwimsett5@etsy.com", "street": "820 Oneill Street", "city": "Duluth", "country": "United States" }, { "id": 7, "first_name": "Sheilakathryn", "last_name": "Bealton", "email": "sbealton6@tripadvisor.com", "street": "57245 Jana Place", "city": "Portland", "country": "United States" }, { "id": 8, "first_name": "Glyn", "last_name": "Pyer", "email": "gpyer7@sbwire.com", "street": "3242 Reinke Junction", "city": "Toledo", "country": "United States" }, { "id": 9, "first_name": "Rahel", "last_name": "Bowker", "email": "rbowker8@narod.ru", "street": "4415 Rutledge Center", "city": "Washington", "country": "United States" }, { "id": 10, "first_name": "Harriette", "last_name": "Dany", "email": "hdany9@rakuten.co.jp", "street": "3479 Sage Court", "city": "Milwaukee", "country": "United States" }, { "id": 11, "first_name": "Janot", "last_name": "Seekings", "email": "jseekingsa@java.com", "street": "46885 Village Parkway", "city": "New Haven", "country": "United States" }, { "id": 12, "first_name": "Ede", "last_name": "Joss", "email": "ejossb@nhs.uk", "street": "54317 Forster Street", "city": "Fort Wayne", "country": "United States" }, { "id": 13, "first_name": "Brina", "last_name": "Kerslake", "email": "bkerslakec@issuu.com", "street": "66 Ohio Road", "city": "Atlanta", "country": "United States" }, { "id": 14, "first_name": "Fayth", "last_name": "Duchart", "email": "fduchartd@nymag.com", "street": "69126 Green Ridge Drive", "city": "Scranton", "country": "United States" }, { "id": 15, "first_name": "Olivie", "last_name": "Archibald", "email": "oarchibalde@vkontakte.ru", "street": "155 Mccormick Center", "city": "Shreveport", "country": "United States" }, { "id": 16, "first_name": "Ree", "last_name": "Wiles", "email": "rwilesf@narod.ru", "street": "8 Marcy Terrace", "city": "Amsterdam Nieuw West", "country": "Netherlands" }, { "id": 17, "first_name": "Coleen", "last_name": "Bardell", "email": "cbardellg@ustream.tv", "street": "18310 Kensington Way", "city": "Columbus", "country": "United States" }, { "id": 18, "first_name": "Camilla", "last_name": "Crissil", "email": "ccrissilh@narod.ru", "street": "5751 Mallory Crossing", "city": "Chicago", "country": "United States" }, { "id": 19, "first_name": "Gallagher", "last_name": "McKennan", "email": "gmckennani@wikipedia.org", "street": "443 Lerdahl Point", "city": "Fort Worth", "country": "United States" }, { "id": 20, "first_name": "Gretal", "last_name": "Laughren", "email": "glaughrenj@dot.gov", "street": "950 Debra Trail", "city": "Bryan", "country": "United States" }, { "id": 21, "first_name": "Celina", "last_name": "Piddocke", "email": "cpiddockek@microsoft.com", "street": "28111 Twin Pines Pass", "city": "Greensboro", "country": "United States" }, { "id": 22, "first_name": "Alverta", "last_name": "Scattergood", "email": "ascattergoodl@w3.org", "street": "44397 Pankratz Court", "city": "Sioux Falls", "country": "United States" }, { "id": 23, "first_name": "Codee", "last_name": "Brugden", "email": "cbrugdenm@google.fr", "street": "4643 Fuller Alley", "city": "Oklahoma City", "country": "United States" }, { "id": 24, "first_name": "Olivero", "last_name": "Linfield", "email": "olinfieldn@vistaprint.com", "street": "4903 Pepper Wood Street", "city": "Austin", "country": "United States" }, { "id": 25, "first_name": "Zorine", "last_name": "Getley", "email": "zgetleyo@nps.gov", "street": "51120 Comanche Junction", "city": "Woerden", "country": "Netherlands" }, { "id": 26, "first_name": "Rafael", "last_name": "MacGraith", "email": "rmacgraithp@whitehouse.gov", "street": "46 Mallard Drive", "city": "New York City", "country": "United States" }, { "id": 27, "first_name": "Kaine", "last_name": "Fantini", "email": "kfantiniq@feedburner.com", "street": "842 Annamark Alley", "city": "Orlando", "country": "United States" }, { "id": 28, "first_name": "Dorthea", "last_name": "Carlet", "email": "dcarletr@last.fm", "street": "46600 Ilene Point", "city": "Pittsburgh", "country": "United States" }, { "id": 29, "first_name": "Velvet", "last_name": "Kielty", "email": "vkieltys@last.fm", "street": "495 Dapin Park", "city": "Las Vegas", "country": "United States" }, { "id": 30, "first_name": "Elfie", "last_name": "Wickey", "email": "ewickeyt@blog.com", "street": "12968 Mandrake Park", "city": "Kerkrade", "country": "Netherlands" }, { "id": 31, "first_name": "Ivan", "last_name": "Prigmore", "email": "iprigmoreu@narod.ru", "street": "82693 Mitchell Pass", "city": "Omaha", "country": "United States" }, { "id": 32, "first_name": "Christos", "last_name": "Daughton", "email": "cdaughtonv@si.edu", "street": "78 Dexter Crossing", "city": "Vlissingen", "country": "Netherlands" }, { "id": 33, "first_name": "Bethina", "last_name": "Yarnton", "email": "byarntonw@istockphoto.com", "street": "4140 Loeprich Junction", "city": "Bradenton", "country": "United States" }, { "id": 34, "first_name": "Erwin", "last_name": "Toland", "email": "etolandx@cnet.com", "street": "624 Blue Bill Park Park", "city": "Boise", "country": "United States" }, { "id": 35, "first_name": "Durand", "last_name": "Henrot", "email": "dhenroty@surveymonkey.com", "street": "00231 Southridge Hill", "city": "Houston", "country": "United States" }, { "id": 36, "first_name": "Frederik", "last_name": "Penkethman", "email": "fpenkethmanz@amazon.co.jp", "street": "42 Goodland Street", "city": "Colorado Springs", "country": "United States" }, { "id": 37, "first_name": "Jacquie", "last_name": "Scrivinor", "email": "jscrivinor10@wikipedia.org", "street": "18040 Anhalt Park", "city": "Midland", "country": "United States" }, { "id": 38, "first_name": "Herta", "last_name": "Trengrouse", "email": "htrengrouse11@tiny.cc", "street": "70124 Hoepker Point", "city": "'s-Hertogenbosch", "country": "Netherlands" }, { "id": 39, "first_name": "Babita", "last_name": "Tott", "email": "btott12@i2i.jp", "street": "8439 Portage Alley", "city": "Boulder", "country": "United States" }, { "id": 40, "first_name": "Raquel", "last_name": "Doram", "email": "rdoram13@360.cn", "street": "6 Crownhardt Hill", "city": "Brooklyn", "country": "United States" }, { "id": 41, "first_name": "Iolande", "last_name": "Gouldbourn", "email": "igouldbourn14@adobe.com", "street": "125 Logan Trail", "city": "Asheville", "country": "United States" }, { "id": 42, "first_name": "Alwin", "last_name": "Corss", "email": "acorss15@wiley.com", "street": "26 Arizona Park", "city": "Boston", "country": "United States" }, { "id": 43, "first_name": "Glen", "last_name": "Frentz", "email": "gfrentz16@princeton.edu", "street": "836 Butternut Court", "city": "Hannover", "country": "Germany" }, { "id": 44, "first_name": "Betsey", "last_name": "Morgen", "email": "bmorgen17@berkeley.edu", "street": "343 American Ash Circle", "city": "Herne", "country": "Germany" }, { "id": 45, "first_name": "Georgeanne", "last_name": "Gherardini", "email": "ggherardini18@patch.com", "street": "8 Glendale Drive", "city": "Portland", "country": "United States" }, { "id": 46, "first_name": "Wes", "last_name": "Stuttard", "email": "wstuttard19@paginegialle.it", "street": "810 Randy Avenue", "city": "Dayton", "country": "United States" }, { "id": 47, "first_name": "Manuel", "last_name": "Hummerston", "email": "mhummerston1a@unicef.org", "street": "9 Delaware Road", "city": "Wichita", "country": "United States" }, { "id": 48, "first_name": "Netty", "last_name": "Santry", "email": "nsantry1b@people.com.cn", "street": "7830 North Point", "city": "Rotterdam postbusnummers", "country": "Netherlands" }, { "id": 49, "first_name": "Tansy", "last_name": "Mazzilli", "email": "tmazzilli1c@t-online.de", "street": "8152 Buhler Avenue", "city": "Annapolis", "country": "United States" }, { "id": 50, "first_name": "Remus", "last_name": "Coultar", "email": "rcoultar1d@ovh.net", "street": "5935 Ohio Avenue", "city": "Elmira", "country": "United States" } ] ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/environments/environment.prod.ts ================================================ export const environment = { production: true }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/environments/environment.ts ================================================ // This file can be replaced during build by using the `fileReplacements` array. // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. export const environment = { production: false }; /* * For easier debugging in development mode, you can import the following file * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. * * This import should be commented out in production mode because it will have a negative impact * on performance if an error is thrown. */ // import 'zone.js/dist/zone-error'; // Included with Angular CLI. ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/index.html ================================================ Demo App ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/main.ts ================================================ import 'hammerjs'; import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err)); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/polyfills.ts ================================================ /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. * * This file is divided into 2 sections: * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. * 2. Application imports. Files imported after ZoneJS that should be loaded before your main * file. * * The current setup is for so-called "evergreen" browsers; the last versions of browsers that * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** * BROWSER POLYFILLS */ /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. /** * Web Animations `@angular/platform-browser/animations` * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). */ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags * because those flags need to be set before `zone.js` being loaded, and webpack * will put import in the top of bundle, so user need to create a separate file * in this directory (for example: zone-flags.ts), and put the following flags * into that file, and then add the following code before importing zone.js. * import './zone-flags.ts'; * * The flags allowed in zone-flags.ts are listed here. * * The following flags will work for all browsers. * * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames * * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * with the following flag, it will bypass `zone.js` patch for IE/Edge * * (window as any).__Zone_enable_cross_context_check = true; * */ /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ /*************************************************************************************************** * SCULLY IMPORTS */ // tslint:disable-next-line: align import 'zone.js/dist/task-tracking'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/styles.scss ================================================ /* You can add global styles to this file, and also import other style files */ html, body { height: 100%; } body { margin: 0; font-family: Roboto, 'Helvetica Neue', sans-serif; } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/tsconfig.app.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": [] }, "files": ["src/main.ts", "src/polyfills.ts"], "include": ["src/**/*.ts"], "exclude": ["src/test.ts", "src/**/*.spec.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app/tsconfig.spec.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts", "src/polyfills.ts"], "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/cypress.json ================================================ { "fileServerFolder": "./", "fixturesFolder": "./src/fixtures", "integrationFolder": "./src/integration", "pluginsFile": "./src/plugins/index.js", "supportFile": "./src/support/index.ts", "video": true, "videosFolder": "../../dist/out-tsc/apps/ng-cli-app-e2e/videos", "screenshotsFolder": "../../dist/out-tsc/apps/ng-cli-app-e2e/screenshots", "chromeWebSecurity": false } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/src/fixtures/example.json ================================================ { "name": "Using fixtures to represent data", "email": "hello@cypress.io" } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/src/integration/app.spec.ts ================================================ describe('app', () => { beforeEach(() => cy.visit('/')); it('should list all nav items', () => { cy.findByText('Home').click(); cy.findByText('Customers').click(); cy.url().should('contain', '/auth/login'); }); it('should fail login', () => { cy.login('something', 'badpass'); cy.findByText('Error: Wrong user or password'); }); it('should login', () => { cy.login('test', 'goodpass'); cy.url().should('contain', '/home'); }); it('should navigate to routes', () => { cy.login('test', 'goodpass'); cy.findByText('Home').click(); cy.url().should('contain', '/home'); cy.findByText('Customers').click(); cy.url().should('contain', '/customers'); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/src/integration/customers.spec.ts ================================================ describe('customers', () => { beforeEach(() => { cy.login('test', 'goodpass'); cy.findByText('Customers').click(); }); it('should display correctly', () => { cy.wait(30000); cy.findByText('Customer Data'); cy.findByRole('grid') .get('tbody tr:first-child') .should('contain', '1') .should('contain', 'Waulker'); }); it('should sort by first name', () => { cy.findByText('first_name').click(); cy.findByRole('grid') .get('tbody tr:first-child') .should('contain', '22') .should('contain', 'Scattergood'); cy.findByText('first_name').click(); cy.findByRole('grid') .get('tbody tr:first-child') .should('contain', '25') .should('contain', 'Getley'); cy.findByText('first_name').click(); cy.findByRole('grid') .get('tbody tr:first-child') .should('contain', '1') .should('contain', 'Waulker'); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/src/integration/home.spec.ts ================================================ describe('home', () => { beforeEach(() => cy.login('test', 'goodpass')); it('should display correctly', () => { cy.wait(30000); cy.findByText('Welcome to the Demo App'); cy.findByText('🥳 Customer of the day'); cy.contains('mat-icon', 'person'); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/src/plugins/index.js ================================================ const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); // *********************************************************** // This example plugins/index.js can be used to load plugins // // You can change the location of this file or turn off loading // the plugins file with the 'pluginsFile' configuration option. // // You can read more here: // https://on.cypress.io/plugins-guide // *********************************************************** // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) module.exports = function(on, config) { on('file:preprocessor', preprocessTypescript(config)); // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/src/support/app.po.ts ================================================ export const getGreeting = () => cy.get('h1'); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/src/support/commands.ts ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** // // // -- This is a parent command -- // Cypress.Commands.add("login", (email, password) => { ... }) // // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) import '@testing-library/cypress/add-commands'; // tslint:disable-next-line: no-namespace declare global { namespace Cypress { interface Chainable { login(email: string, password: string): void; } } } Cypress.Commands.add('login', (email, password) => { cy.visit('/auth/login'); cy.findByLabelText('Username').type(email); cy.findByLabelText('Password').type(password); cy.contains('button', 'Login').click(); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/src/support/index.ts ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/tsconfig.e2e.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "sourceMap": false, "outDir": "../../dist/out-tsc" }, "include": ["src/**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/ng-cli-app-e2e/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["cypress", "@types/testing-library__cypress", "node"] }, "include": ["**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/shared-components-e2e/cypress.json ================================================ { "fileServerFolder": ".", "fixturesFolder": "./src/fixtures", "integrationFolder": "./src/integration", "modifyObstructiveCode": false, "pluginsFile": "./src/plugins/index", "supportFile": "./src/support/index.ts", "video": true, "videosFolder": "../../dist/cypress/apps/shared-components-e2e/videos", "screenshotsFolder": "../../dist/cypress/apps/shared-components-e2e/screenshots", "chromeWebSecurity": false, "baseUrl": "http://localhost:4400" } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/shared-components-e2e/src/fixtures/example.json ================================================ { "name": "Using fixtures to represent data", "email": "hello@cypress.io" } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/shared-components-e2e/src/integration/info-box/info-box.component.spec.ts ================================================ describe('shared-components', () => { it('should render the component', () => { cy.visit( '/iframe.html?id=infoboxcomponent--primary&knob-icon=person&knob-message=O-H-I-O' ); cy.get('app-info-box').should('exist'); cy.contains('Go Bucks'); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/shared-components-e2e/src/integration/navigation/navigation.component.spec.ts ================================================ describe('shared-components', () => { beforeEach(() => cy.visit('/iframe.html?id=navigationcomponent--primary')); it('should render the component', () => { cy.get('app-navigation').should('exist'); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/shared-components-e2e/src/plugins/index.js ================================================ // *********************************************************** // This example plugins/index.js can be used to load plugins // // You can change the location of this file or turn off loading // the plugins file with the 'pluginsFile' configuration option. // // You can read more here: // https://on.cypress.io/plugins-guide // *********************************************************** // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config // Preprocess Typescript on('file:preprocessor', preprocessTypescript(config)); }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/shared-components-e2e/src/support/commands.ts ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** // eslint-disable-next-line @typescript-eslint/no-namespace declare namespace Cypress { interface Chainable { login(email: string, password: string): void; } } // // -- This is a parent command -- Cypress.Commands.add('login', (email, password) => { console.log('Custom command example: Login', email, password); }); // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/shared-components-e2e/src/support/index.ts ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/shared-components-e2e/tsconfig.e2e.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "sourceMap": false, "outDir": "../../dist/out-tsc" }, "include": ["src/**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/shared-components-e2e/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["cypress", "node"] }, "include": ["**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/apps/shared-components-e2e/tslint.json ================================================ {"extends":"../../tslint.json","rules":[]} ================================================ FILE: packages/guess-parser/test/fixtures/nx/docs/_config.yml ================================================ theme: jekyll-theme-minimal title: Angular monorepo example using Nx ================================================ FILE: packages/guess-parser/test/fixtures/nx/docs/index.md ================================================ --- layout: default --- # Angular monorepo example using Nx This app shows the process of refactoring an Angular app to a monorepo with the help of [Nx](https://nx.dev/angular) # Slides The slides for the corresponding sessions of these samples can be found here: [Slides](./scalable_angular_architecture_with_nx.pdf). ================================================ FILE: packages/guess-parser/test/fixtures/nx/index.js ================================================ const { parseAngularRoutes } = require('guess-parser'); console.log(parseAngularRoutes('apps/ng-cli-app/tsconfig.app.json')); ================================================ FILE: packages/guess-parser/test/fixtures/nx/jest.config.js ================================================ module.exports = { testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'], transform: { '^.+\\.(ts|js|html)$': 'ts-jest' }, resolver: '@nrwl/jest/plugins/resolver', moduleFileExtensions: ['ts', 'js', 'html'], coverageReporters: ['html'], passWithNoTests: true }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html const { join } = require('path'); const { constants } = require('karma'); module.exports = () => { return { basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client: { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: join(__dirname, '../../coverage'), reports: ['html', 'lcovonly'], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: constants.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: true }; }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/.gitkeep ================================================ ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/README.md ================================================ # auth This library was generated with [Nx](https://nx.dev). ## Running unit tests Run `nx test auth` to execute the unit tests. ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/jest.config.js ================================================ module.exports = { name: 'auth', preset: '../../jest.config.js', coverageDirectory: '../../coverage/libs/auth', snapshotSerializers: [ 'jest-preset-angular/AngularSnapshotSerializer.js', 'jest-preset-angular/HTMLCommentSerializer.js' ] }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/index.ts ================================================ export * from './lib/auth.module'; export * from './lib/auth-routing.module'; export * from './lib/auth.guard'; export * from './lib/auth.service'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/auth-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { LoginComponent } from './login/login.component'; export const AUTH_ROUTES: Routes = [ { path: 'login', component: LoginComponent } ]; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/auth.guard.spec.ts ================================================ import { TestBed, async, inject } from '@angular/core/testing'; import { AuthGuard } from './auth.guard'; import { RouterTestingModule } from '@angular/router/testing'; describe('AuthGuard', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule], providers: [AuthGuard] }); }); it('should ...', inject([AuthGuard], (guard: AuthGuard) => { expect(guard).toBeTruthy(); })); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/auth.guard.ts ================================================ import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router'; import { Observable } from 'rxjs'; import { AuthService } from './auth.service'; import { take, map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable { return this.authService.isAuthenticated$.pipe( take(1), map(authenticated => authenticated ? true : this.router.parseUrl('/auth/login') ) ); } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/auth.module.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { AuthModule } from './auth.module'; describe('AuthModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [AuthModule] }).compileComponents(); })); it('should create', () => { expect(AuthModule).toBeDefined(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/auth.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatInputModule } from '@angular/material/input'; import { LoginComponent } from './login/login.component'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { ReactiveFormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; import { SharedComponentsModule } from '@ng-cli-app/shared/components'; @NgModule({ declarations: [LoginComponent], imports: [ CommonModule, SharedComponentsModule, MatInputModule, MatButtonModule, ReactiveFormsModule, MatCardModule, MatIconModule ] }) export class AuthModule {} ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/auth.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { AuthService } from './auth.service'; import { RouterTestingModule } from '@angular/router/testing'; describe('AuthService', () => { beforeEach(() => TestBed.configureTestingModule({ imports: [RouterTestingModule] }) ); it('should be created', () => { const service: AuthService = TestBed.get(AuthService); expect(service).toBeTruthy(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/auth.service.ts ================================================ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; import { map } from 'rxjs/operators'; import { Router } from '@angular/router'; @Injectable({ providedIn: 'root' }) export class AuthService { private userSubject: BehaviorSubject = new BehaviorSubject(null); public currentUser$ = this.userSubject.asObservable(); public isAuthenticated$ = this.currentUser$.pipe(map(user => !!user)); constructor(private router: Router) {} public login(username: string, password: string): Observable { if ('test' === username) { const user = { username, fullName: 'Test User' }; this.userSubject.next(user); this.router.navigateByUrl('/'); return of(user); } return throwError(new Error('Wrong user or password')); } public logout() { this.userSubject.next(null); this.router.navigateByUrl('/auth/login'); } } export interface User { username: string; fullName: string; } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/login/login.component.html ================================================

Login

================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/login/login.component.scss ================================================ :host { display: flex; flex-direction: column; } form { display: flex; flex-direction: column; max-width: 250px; } app-info-box { margin-bottom: 2rem; } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/login/login.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { ReactiveFormsModule } from '@angular/forms'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { LoginComponent } from './login.component'; import { AuthService } from '../auth.service'; import { SharedComponentsModule } from '@ng-cli-app/shared/components'; describe('LoginComponent', () => { let component: LoginComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, SharedComponentsModule, MatInputModule, MatButtonModule, ReactiveFormsModule, MatCardModule, MatIconModule ], declarations: [LoginComponent], providers: [ { provide: AuthService, useValue: {} } ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(LoginComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/lib/login/login.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { AuthService } from '../auth.service'; import { catchError, tap, take } from 'rxjs/operators'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.scss'] }) export class LoginComponent implements OnInit { public form: FormGroup; public error: string; constructor(fb: FormBuilder, private authService: AuthService) { this.form = fb.group({ username: ['', Validators.required], password: ['', Validators.required] }); } ngOnInit() {} public onSubmit() { if (this.form.valid) { this.authService .login(this.form.value.username, this.form.value.password) .pipe( take(1), catchError(error => (this.error = error.toString())) ) .subscribe(); } } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "declaration": true, "inlineSources": true, "types": [], "lib": ["dom", "es2018"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": ["src/test-setup.ts", "**/*.spec.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"], "emitDecoratorMetadata": true }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/auth/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "app", "camelCase"], "component-selector": [true, "element", "app", "kebab-case"] } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/README.md ================================================ # customers-data This library was generated with [Nx](https://nx.dev). ## Running unit tests Run `nx test customers-data` to execute the unit tests. ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/jest.config.js ================================================ module.exports = { name: 'customers-data', preset: '../../../jest.config.js', coverageDirectory: '../../../coverage/libs/customers/data', snapshotSerializers: [ 'jest-preset-angular/AngularSnapshotSerializer.js', 'jest-preset-angular/HTMLCommentSerializer.js' ] }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/src/index.ts ================================================ export * from './lib/customer.service'; export * from './lib/customer.model'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/src/lib/customer.model.ts ================================================ export interface Customer { id: number; first_name: string; last_name: string; email: string; street: string; city: string; country: string; } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/src/lib/customer.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { CustomerService } from './customer.service'; import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('CustomerService', () => { beforeEach(() => TestBed.configureTestingModule({ imports: [HttpClientTestingModule] }) ); it('should be created', () => { const service: CustomerService = TestBed.get(CustomerService); expect(service).toBeTruthy(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/src/lib/customer.service.ts ================================================ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Customer } from './customer.model'; import { shareReplay, map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class CustomerService { constructor(private httpClient: HttpClient) {} public getCustomers(): Observable { return this.httpClient .get('/assets/customers.json') .pipe(shareReplay(1)); } public getCustomerOfTheDay(): Observable { return this.getCustomers().pipe( map(customers => customers[Math.floor(Math.random() * customers.length)]) ); } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/tsconfig.json ================================================ { "extends": "../../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../../dist/out-tsc", "target": "es2015", "declaration": true, "inlineSources": true, "types": [], "lib": ["dom", "es2018"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": ["src/test-setup.ts", "**/*.spec.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"], "emitDecoratorMetadata": true }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/data/tslint.json ================================================ { "extends": "../../../tslint.json", "rules": { "directive-selector": [true, "attribute", "app", "camelCase"], "component-selector": [true, "element", "app", "kebab-case"] } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/README.md ================================================ # feat-customers This library was generated with [Nx](https://nx.dev). ## Running unit tests Run `nx test feat-customers` to execute the unit tests. ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/jest.config.js ================================================ module.exports = { name: 'feat-customers', preset: '../../../jest.config.js', coverageDirectory: '../../../coverage/libs/customers/ui', snapshotSerializers: [ 'jest-preset-angular/AngularSnapshotSerializer.js', 'jest-preset-angular/HTMLCommentSerializer.js' ] }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/index.ts ================================================ export * from './lib/customers-ui.module'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customer-list/customer-list-datasource.ts ================================================ import { DataSource } from '@angular/cdk/collections'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { map } from 'rxjs/operators'; import { Observable, merge, Subscription, of } from 'rxjs'; import { CustomerService, Customer } from '@ng-cli-app/customers/data'; /** * Data source for the CustomerList view. This class should * encapsulate all logic for fetching and manipulating the displayed data * (including sorting, pagination, and filtering). */ export class CustomerListDataSource extends DataSource { paginator: MatPaginator; sort: MatSort; private data: Customer[] = []; private subscription = new Subscription(); constructor(private customerService: CustomerService) { super(); } /** * Connect this data source to the table. The table will only update when * the returned stream emits new items. * @returns A stream of the items to be rendered. */ connect(): Observable { const data$ = this.customerService.getCustomers(); this.subscription.add( data$.subscribe(data => { this.data = data; this.paginator.length = data.length; }) ); // Combine everything that affects the rendered data into one update // stream for the data-table to consume. const dataMutations = [data$, this.paginator.page, this.sort.sortChange]; return merge(...dataMutations).pipe( map(() => { return this.getPagedData(this.getSortedData([...this.data])); }) ); } /** * Called when the table is being destroyed. Use this function, to clean up * any open connections or free any held resources that were set up during connect. */ disconnect() { this.subscription.unsubscribe(); } /** * Paginate the data (client-side). If you're using server-side pagination, * this would be replaced by requesting the appropriate data from the server. */ private getPagedData(data: Customer[]) { const startIndex = this.paginator.pageIndex * this.paginator.pageSize; return data.splice(startIndex, this.paginator.pageSize); } /** * Sort the data (client-side). If you're using server-side sorting, * this would be replaced by requesting the appropriate data from the server. */ private getSortedData(data: Customer[]) { if (!this.sort.active || this.sort.direction === '') { return data; } return data.sort((a, b) => { const isAsc = this.sort.direction === 'asc'; switch (this.sort.active) { case 'first_name': return compare(a.first_name, b.first_name, isAsc); case 'id': return compare(+a.id, +b.id, isAsc); default: return 0; } }); } } /** Simple sort comparator for example ID/Name columns (for client-side sorting). */ function compare(a, b, isAsc) { return (a < b ? -1 : 1) * (isAsc ? 1 : -1); } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customer-list/customer-list.component.html ================================================
{{ col }} {{ row[col] }}
================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customer-list/customer-list.component.scss ================================================ .full-width-table { width: 100%; } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customer-list/customer-list.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; import { CustomerListComponent } from './customer-list.component'; import { CustomerService } from '@ng-cli-app/customers/data'; describe('CustomerListComponent', () => { let component: CustomerListComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [CustomerListComponent], imports: [ NoopAnimationsModule, MatPaginatorModule, MatSortModule, MatTableModule ], providers: [{ provide: CustomerService, useValue: {} }] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(CustomerListComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should compile', () => { expect(component).toBeTruthy(); }); new Array(100) .fill('Test') .fill('Test', 0, 1000) .forEach(() => { it('should be a good test', done => { expect(true).toBe(true); setTimeout(() => done(), 100); }); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customer-list/customer-list.component.ts ================================================ import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { MatTable } from '@angular/material/table'; import { CustomerListDataSource } from './customer-list-datasource'; import { Customer, CustomerService } from '@ng-cli-app/customers/data'; @Component({ selector: 'app-customer-list', templateUrl: './customer-list.component.html', styleUrls: ['./customer-list.component.scss'] }) export class CustomerListComponent implements AfterViewInit, OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @ViewChild(MatTable) table: MatTable; dataSource: CustomerListDataSource; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns: (keyof Customer)[] = [ 'id', 'first_name', 'last_name', 'email', 'city', 'country' ]; constructor(private customerService: CustomerService) {} ngOnInit() { this.dataSource = new CustomerListDataSource(this.customerService); } ngAfterViewInit() { this.dataSource.sort = this.sort; this.dataSource.paginator = this.paginator; this.table.dataSource = this.dataSource; } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customers-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { CustomersComponent } from './customers.component'; import { CustomerListComponent } from './customer-list/customer-list.component'; export const routes: Routes = [ { path: '', component: CustomersComponent, children: [ { path: '', pathMatch: 'full', redirectTo: 'list' }, { path: 'list', component: CustomerListComponent } ] } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class CustomersRoutingModule {} ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customers-ui.module.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { CustomersUiModule } from './feat-customers.module'; describe('CustomersUiModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [CustomersUiModule] }).compileComponents(); })); it('should create', () => { expect(CustomersUiModule).toBeDefined(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customers-ui.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { CustomersRoutingModule } from './customers-routing.module'; import { CustomersComponent } from './customers.component'; import { CustomerListComponent } from './customer-list/customer-list.component'; import { MatTableModule } from '@angular/material/table'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatSortModule } from '@angular/material/sort'; @NgModule({ declarations: [CustomersComponent, CustomerListComponent], imports: [ CommonModule, CustomersRoutingModule, MatTableModule, MatPaginatorModule, MatSortModule ], exports: [CustomerListComponent] }) export class CustomersUiModule {} ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customers.component.html ================================================

Customer Data

================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customers.component.scss ================================================ ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customers.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CustomersComponent } from './customers.component'; import { RouterTestingModule } from '@angular/router/testing'; describe('CustomersComponent', () => { let component: CustomersComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule], declarations: [CustomersComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(CustomersComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/lib/customers.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-customers', templateUrl: './customers.component.html', styleUrls: ['./customers.component.scss'] }) export class CustomersComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/tsconfig.json ================================================ { "extends": "../../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../../dist/out-tsc", "target": "es2015", "declaration": true, "inlineSources": true, "types": [], "lib": ["dom", "es2018"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": ["src/test-setup.ts", "**/*.spec.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"], "emitDecoratorMetadata": true }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/customers/ui/tslint.json ================================================ { "extends": "../../../tslint.json", "rules": { "directive-selector": [true, "attribute", "app", "camelCase"], "component-selector": [true, "element", "app", "kebab-case"] } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/.storybook/addons.js ================================================ import '../../../../.storybook/addons'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/.storybook/config.js ================================================ import { configure, addDecorator } from '@storybook/angular'; import { withKnobs } from '@storybook/addon-knobs'; addDecorator(withKnobs); configure(require.context('../src/lib', true, /\.stories\.tsx?$/), module); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/.storybook/tsconfig.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "emitDecoratorMetadata": true }, "exclude": ["../src/test.ts", "../**/*.spec.ts"], "include": ["../src/**/*"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/.storybook/webpack.config.js ================================================ const rootWebpackConfig = require('../../../../.storybook/webpack.config'); // Export a function. Accept the base config as the only param. module.exports = async ({ config, mode }) => { config = await rootWebpackConfig({ config, mode }); return config; }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/README.md ================================================ # feat-home This library was generated with [Nx](https://nx.dev). ## Running unit tests Run `nx test feat-home` to execute the unit tests. ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/jest.config.js ================================================ module.exports = { name: 'feat-home', preset: '../../../jest.config.js', coverageDirectory: '../../../coverage/libs/home/ui', snapshotSerializers: [ 'jest-preset-angular/AngularSnapshotSerializer.js', 'jest-preset-angular/HTMLCommentSerializer.js' ] }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/src/index.ts ================================================ export * from './lib/home-ui.module'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/src/lib/home-ui.module.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { HomeUiModule } from './feat-home.module'; describe('HomeUiModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [HomeUiModule] }).compileComponents(); })); it('should create', () => { expect(HomeUiModule).toBeDefined(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/src/lib/home-ui.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HomeComponent } from './home.component'; import { SharedComponentsModule } from '@ng-cli-app/shared/components'; import { Routes, RouterModule } from '@angular/router'; export const routes: Routes = [{ path: '', component: HomeComponent }]; @NgModule({ declarations: [HomeComponent], imports: [CommonModule, SharedComponentsModule, RouterModule.forChild(routes)] }) export class HomeUiModule {} ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/src/lib/home.component.html ================================================

Welcome to the Demo App


🥳 Customer of the day

================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/src/lib/home.component.scss ================================================ ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/src/lib/home.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { HomeComponent } from './home.component'; import { EMPTY } from 'rxjs'; import { SharedComponentsModule } from '@ng-cli-app/shared/components'; import { CustomerService } from '@ng-cli-app/customers/data'; describe('HomeComponent', () => { let component: HomeComponent; let fixture: ComponentFixture; beforeEach(async(() => { const customerServiceMock = { getCustomerOfTheDay: () => EMPTY }; TestBed.configureTestingModule({ imports: [SharedComponentsModule], declarations: [HomeComponent], providers: [{ provide: CustomerService, useValue: customerServiceMock }] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(HomeComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/src/lib/home.component.stories.ts ================================================ import { HomeComponent } from './home.component'; import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { HttpClientModule } from '@angular/common/http'; import { SharedComponentsModule } from '@ng-cli-app/shared/components'; import { MatButtonModule } from '@angular/material/button'; import { AuthModule } from '@ng-cli-app/auth'; import { RouterModule } from '@angular/router'; import { APP_BASE_HREF } from '@angular/common'; export default { title: 'HomeComponent' }; export const primary = () => ({ moduleMetadata: { imports: [ BrowserModule, HttpClientModule, RouterModule.forRoot([ { path: '**', component: HomeComponent } ]), BrowserAnimationsModule, SharedComponentsModule, MatButtonModule, AuthModule ], providers: [ { provide: APP_BASE_HREF, useValue: '/' } ] }, component: HomeComponent, props: {} }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/src/lib/home.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { map, filter } from 'rxjs/operators'; import { CustomerService, Customer } from '@ng-cli-app/customers/data'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'] }) export class HomeComponent implements OnInit { public customerOfTheDay$: Observable; constructor(private customerService: CustomerService) {} ngOnInit() { this.customerOfTheDay$ = this.customerService.getCustomerOfTheDay().pipe( filter(customer => !!customer), map( (customer: Customer) => customer.first_name + ' ' + customer.last_name ) ); } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/tsconfig.json ================================================ { "extends": "../../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../../dist/out-tsc", "target": "es2015", "declaration": true, "inlineSources": true, "types": [], "lib": [ "dom", "es2018" ] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": [ "src/test-setup.ts", "**/*.spec.ts", "**/*.stories.ts" ] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"], "emitDecoratorMetadata": true }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/home/ui/tslint.json ================================================ { "extends": "../../../tslint.json", "rules": { "directive-selector": [true, "attribute", "app", "camelCase"], "component-selector": [true, "element", "app", "kebab-case"] } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/.storybook/addons.js ================================================ import '../../../../.storybook/addons'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/.storybook/config.js ================================================ import { configure, addDecorator } from '@storybook/angular'; import { withKnobs } from '@storybook/addon-knobs'; addDecorator(withKnobs); configure(require.context('../src/lib', true, /\.stories\.tsx?$/), module); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/.storybook/preview-head.html ================================================ ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/.storybook/tsconfig.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "emitDecoratorMetadata": true }, "exclude": ["../src/test.ts", "../**/*.spec.ts"], "include": ["../src/**/*"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/.storybook/webpack.config.js ================================================ const rootWebpackConfig = require('../../../../.storybook/webpack.config'); // Export a function. Accept the base config as the only param. module.exports = async ({ config, mode }) => { config = await rootWebpackConfig({ config, mode }); return config; }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/README.md ================================================ # shared-components This library was generated with [Nx](https://nx.dev). ## Running unit tests Run `nx test shared-components` to execute the unit tests. ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/jest.config.js ================================================ module.exports = { name: 'shared-components', preset: '../../../jest.config.js', coverageDirectory: '../../../coverage/libs/shared/components', snapshotSerializers: [ 'jest-preset-angular/AngularSnapshotSerializer.js', 'jest-preset-angular/HTMLCommentSerializer.js' ] }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/index.ts ================================================ export * from './lib/shared-components.module'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/info-box/info-box.component.html ================================================
{{ icon }}{{ message }}
================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/info-box/info-box.component.scss ================================================ .box { display: flex; flex-direction: row; align-items: center; span { margin-left: 1rem; } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/info-box/info-box.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { InfoBoxComponent } from './info-box.component'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; describe('InfoBoxComponent', () => { let component: InfoBoxComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [MatCardModule, MatIconModule], declarations: [InfoBoxComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(InfoBoxComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/info-box/info-box.component.stories.ts ================================================ import { text, number, boolean } from '@storybook/addon-knobs'; import { SharedComponentsModule } from '../shared-components.module'; import { InfoBoxComponent } from './info-box.component'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; export default { title: 'InfoBoxComponent' }; export const primary = () => ({ moduleMetadata: { imports: [MatCardModule, MatIconModule] }, component: InfoBoxComponent, props: { icon: text('icon', ''), message: text('message', '') } }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/info-box/info-box.component.ts ================================================ import { Component, OnInit, Input } from '@angular/core'; @Component({ selector: 'app-info-box', templateUrl: './info-box.component.html', styleUrls: ['./info-box.component.scss'] }) export class InfoBoxComponent implements OnInit { @Input() icon: string; @Input() message: string; constructor() {} ngOnInit() {} } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/navigation/navigation.component.html ================================================ menu Menu home Home person Customers Demo App
================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/navigation/navigation.component.scss ================================================ .sidenav-container { height: 100%; } .sidenav { width: 200px; } .sidenav .mat-toolbar { background: inherit; } .mat-toolbar.mat-primary { position: sticky; top: 0; z-index: 1; } .active { background: rgba(0, 0, 0, 0.04); } main { padding: 1rem; } .spacer { flex: 1 0 auto; } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/navigation/navigation.component.spec.ts ================================================ import { LayoutModule } from '@angular/cdk/layout'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatListModule } from '@angular/material/list'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatToolbarModule } from '@angular/material/toolbar'; import { NavigationComponent } from './navigation.component'; describe('NavigationComponent', () => { let component: NavigationComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [NavigationComponent], imports: [ NoopAnimationsModule, LayoutModule, MatButtonModule, MatIconModule, MatListModule, MatSidenavModule, MatToolbarModule ], providers: [] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(NavigationComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should compile', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/navigation/navigation.component.stories.ts ================================================ import { SharedComponentsModule } from '../shared-components.module'; import { NavigationComponent } from './navigation.component'; export default { title: 'NavigationComponent' } export const primary = () => ({ moduleMetadata: { imports: [] }, component: NavigationComponent, props: { } }) ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/navigation/navigation.component.ts ================================================ import { Component } from '@angular/core'; import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; import { Observable } from 'rxjs'; import { map, shareReplay } from 'rxjs/operators'; @Component({ selector: 'app-navigation', templateUrl: './navigation.component.html', styleUrls: ['./navigation.component.scss'] }) export class NavigationComponent { isHandset$: Observable = this.breakpointObserver .observe(Breakpoints.Handset) .pipe( map(result => result.matches), shareReplay() ); constructor(private breakpointObserver: BreakpointObserver) {} } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/shared-components.module.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { SharedComponentsModule } from './shared-components.module'; describe('SharedComponentsModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [SharedComponentsModule] }).compileComponents(); })); it('should create', () => { expect(SharedComponentsModule).toBeDefined(); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/lib/shared-components.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { LayoutModule } from '@angular/cdk/layout'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatButtonModule } from '@angular/material/button'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatIconModule } from '@angular/material/icon'; import { MatListModule } from '@angular/material/list'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { NavigationComponent } from './navigation/navigation.component'; import { InfoBoxComponent } from './info-box/info-box.component'; @NgModule({ declarations: [NavigationComponent, InfoBoxComponent], imports: [ CommonModule, RouterModule, LayoutModule, MatCardModule, MatToolbarModule, MatButtonModule, MatSidenavModule, MatIconModule, MatListModule ], exports: [NavigationComponent, InfoBoxComponent] }) export class SharedComponentsModule {} ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/tsconfig.json ================================================ { "extends": "../../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../../dist/out-tsc", "target": "es2015", "declaration": true, "inlineSources": true, "types": [], "lib": [ "dom", "es2018" ] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": [ "src/test-setup.ts", "**/*.spec.ts", "**/*.stories.ts" ] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"], "emitDecoratorMetadata": true }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/libs/shared/components/tslint.json ================================================ { "extends": "../../../tslint.json", "rules": { "directive-selector": [true, "attribute", "app", "camelCase"], "component-selector": [true, "element", "app", "kebab-case"] } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/nx.json ================================================ { "npmScope": "ng-cli-app", "implicitDependencies": { "angular.json": "*", "package.json": "*", "tsconfig.json": "*", "tslint.json": "*", "nx.json": "*" }, "projects": { "ng-cli-app": { "tags": [ "scope:app", "type:app" ] }, "ng-cli-app-e2e": { "tags": [ "scope:app", "type:e2e" ] }, "shared-components": { "tags": [ "scope:shared", "type:ui" ] }, "auth": { "tags": [ "scope:auth", "type:ui" ] }, "feat-customers": { "tags": [ "scope:customers", "type:ui" ] }, "customers-data": { "tags": [ "scope:customers", "type:data" ] }, "feat-home": { "tags": [ "scope:home", "type:ui" ] }, "shared-components-e2e": { "tags": [] }, "feat-home-e2e": { "tags": [] } } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/package.json ================================================ { "name": "ng-cli-app", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", "lint": "nx workspace-lint && ng lint", "e2e": "ng e2e", "nx": "nx", "affected:apps": "nx affected:apps", "affected:libs": "nx affected:libs", "affected:build": "nx affected:build", "affected:e2e": "nx affected:e2e", "affected:test": "nx affected:test", "affected:lint": "nx affected:lint", "affected:dep-graph": "nx affected:dep-graph", "affected": "nx affected", "format": "nx format:write", "format:write": "nx format:write", "format:check": "nx format:check", "update": "ng update @nrwl/workspace", "update:check": "ng update", "dep-graph": "nx dep-graph", "workspace-schematic": "nx workspace-schematic", "help": "nx help", "scully": "scully", "scully:serve": "scully serve" }, "private": true, "dependencies": { "@angular/animations": "~9.0.0-rc.6", "@angular/cdk": "~8.2.3", "@angular/cli": "~9.0.0-rc.6", "@angular/common": "~9.0.0-rc.6", "@angular/compiler": "~9.0.0-rc.6", "@angular/core": "~9.0.0-rc.6", "@angular/forms": "~9.0.0-rc.6", "@angular/material": "^8.2.3", "@angular/platform-browser": "~9.0.0-rc.6", "@angular/platform-browser-dynamic": "~9.0.0-rc.6", "@angular/router": "~9.0.0-rc.6", "@nrwl/angular": "8.9.0", "@scullyio/init": "^0.0.8", "@scullyio/ng-lib": "latest", "@scullyio/scully": "latest", "guess-parser": "^0.4.12", "hammerjs": "^2.0.8", "rxjs": "~6.5.3", "tslib": "^1.10.0", "zone.js": "~0.10.2" }, "devDependencies": { "@angular-devkit/build-angular": "~0.900.0-rc.6", "@angular/compiler-cli": "~9.0.0-rc.6", "@angular/language-service": "~9.0.0-rc.6", "@babel/core": "7.5.4", "@nrwl/cypress": "8.9.0", "@nrwl/jest": "8.9.0", "@nrwl/storybook": "^8.9.0", "@nrwl/workspace": "8.9.0", "@storybook/addon-knobs": "5.2.5", "@storybook/angular": "5.2.5", "@storybook/react": "5.2.5", "@testing-library/cypress": "^5.0.2", "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", "@types/jest": "24.0.9", "@types/node": "~8.9.4", "babel-loader": "8.0.6", "codelyzer": "^5.0.0", "cypress": "3.4.1", "jasmine-core": "~3.4.0", "jasmine-spec-reporter": "~4.2.1", "jest": "24.1.0", "jest-preset-angular": "7.1.0", "karma": "~4.1.0", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "~2.0.1", "karma-jasmine": "~2.0.1", "karma-jasmine-html-reporter": "^1.4.0", "prettier": "1.18.2", "protractor": "~5.4.0", "ts-jest": "24.0.0", "ts-node": "~7.0.0", "tslint": "~5.15.0", "typescript": "~3.6.4" } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/scully.config.js ================================================ exports.config = { projectRoot: './apps/ng-cli-app/src', routes: {} }; ================================================ FILE: packages/guess-parser/test/fixtures/nx/tools/schematics/.gitkeep ================================================ ================================================ FILE: packages/guess-parser/test/fixtures/nx/tools/tsconfig.tools.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../dist/out-tsc/tools", "rootDir": ".", "module": "commonjs", "target": "es5", "types": ["node"] }, "include": ["**/*.ts"] } ================================================ FILE: packages/guess-parser/test/fixtures/nx/tsconfig.json ================================================ { "compileOnSave": false, "compilerOptions": { "baseUrl": ".", "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, "downlevelIteration": true, "experimentalDecorators": true, "module": "esnext", "moduleResolution": "node", "importHelpers": true, "target": "es2015", "typeRoots": ["node_modules/@types"], "lib": ["es2018", "dom"], "paths": { "@ng-cli-app/shared/components": ["libs/shared/components/src/index.ts"], "@ng-cli-app/auth": ["libs/auth/src/index.ts"], "@ng-cli-app/customers/ui": ["libs/customers/ui/src/index.ts"], "@ng-cli-app/customers/data": ["libs/customers/data/src/index.ts"], "@ng-cli-app/home/ui": ["libs/home/ui/src/index.ts"] }, "rootDir": "." }, "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true } } ================================================ FILE: packages/guess-parser/test/fixtures/nx/tslint.json ================================================ { "extends": "tslint:recommended", "rules": { "array-type": false, "arrow-parens": false, "deprecation": { "severity": "warning" }, "component-class-suffix": true, "contextual-lifecycle": true, "directive-class-suffix": true, "directive-selector": [true, "attribute", "app", "camelCase"], "component-selector": [true, "element", "app", "kebab-case"], "import-blacklist": [true, "rxjs/Rx"], "interface-name": false, "max-classes-per-file": false, "max-line-length": [true, 140], "member-access": false, "member-ordering": [ true, { "order": [ "static-field", "instance-field", "static-method", "instance-method" ] } ], "no-consecutive-blank-lines": false, "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], "no-empty": false, "no-inferrable-types": [true, "ignore-params"], "no-non-null-assertion": true, "no-redundant-jsdoc": true, "no-switch-case-fall-through": true, "no-var-requires": false, "object-literal-key-quotes": [true, "as-needed"], "object-literal-sort-keys": false, "ordered-imports": false, "quotemark": [true, "single"], "trailing-comma": false, "no-conflicting-lifecycle": true, "no-host-metadata-property": true, "no-input-rename": true, "no-inputs-metadata-property": true, "no-output-native": true, "no-output-on-prefix": true, "no-output-rename": true, "no-outputs-metadata-property": true, "template-banana-in-box": true, "template-no-negated-async": true, "use-lifecycle-interface": true, "use-pipe-transform-interface": true, "nx-enforce-module-boundaries": [ true, { "allow": [], "depConstraints": [ { "sourceTag": "type:app", "onlyDependOnLibsWithTags": ["type:ui", "type:data"] }, { "sourceTag": "type:ui", "onlyDependOnLibsWithTags": ["type:ui", "type:data"] }, { "sourceTag": "type:data", "onlyDependOnLibsWithTags": ["type:data"] }, { "sourceTag": "scope:app", "onlyDependOnLibsWithTags": [ "scope:auth", "scope:customers", "scope:home", "scope:shared" ] }, { "sourceTag": "scope:auth", "onlyDependOnLibsWithTags": ["scope:auth", "scope:shared"] }, { "sourceTag": "scope:shared", "onlyDependOnLibsWithTags": ["scope:shared"] }, { "sourceTag": "scope:customers", "onlyDependOnLibsWithTags": ["scope:customers", "scope:shared"] }, { "sourceTag": "scope:home", "onlyDependOnLibsWithTags": [ "scope:home", "scope:customers", "scope:shared" ] } ] } ] }, "rulesDirectory": ["codelyzer", "node_modules/@nrwl/workspace/src/tslint"] } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/.gitignore ================================================ node_modules /build /*.log *.lock package-lock.json ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/README.md ================================================ # preact-app ## CLI Commands ``` bash # install dependencies npm install # serve with hot reload at localhost:8080 npm run dev # build for production with minification npm run build # test the production build locally npm run serve # run tests with jest and preact-render-spy npm run test ``` For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md). ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/package.json ================================================ { "private": true, "name": "preact-app", "version": "0.0.0", "license": "MIT", "scripts": { "start": "if-env NODE_ENV=production && npm run -s serve || npm run -s dev", "build": "preact build", "serve": "preact build && preact serve", "dev": "preact watch", "lint": "eslint src", "test": "jest ./tests" }, "eslintConfig": { "extends": "eslint-config-synacor" }, "eslintIgnore": [ "build/*" ], "devDependencies": { "eslint": "^4.9.0", "eslint-config-synacor": "^2.0.2", "identity-obj-proxy": "^3.0.0", "if-env": "^1.0.0", "jest": "^21.2.1", "preact-cli": "^2.1.0", "preact-render-spy": "^1.2.1" }, "dependencies": { "preact": "^8.2.6", "preact-async-route": "^2.1.1", "preact-compat": "^3.17.0", "preact-router": "^2.5.7" }, "jest": { "verbose": true, "setupFiles": [ "/src/tests/__mocks__/browserMocks.js" ], "testURL": "http://localhost:8080", "moduleFileExtensions": [ "js", "jsx" ], "moduleDirectories": [ "node_modules" ], "moduleNameMapper": { "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/src/tests/__mocks__/fileMock.js", "\\.(css|less|scss)$": "identity-obj-proxy", "^./style$": "identity-obj-proxy", "^preact$": "/node_modules/preact/dist/preact.min.js", "^react$": "preact-compat", "^react-dom$": "preact-compat", "^create-react-class$": "preact-compat/lib/create-react-class", "^react-addons-css-transition-group$": "preact-css-transition-group" } } } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/.babelrc ================================================ { "presets": [ ["preact-cli/babel", { "modules": "commonjs" }] ] } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/components/app.js ================================================ import { h, Component } from 'preact'; import { Router } from 'preact-router'; import AsyncRoute from 'preact-async-route'; import Info from './info'; import Header from './header'; import Home from '../routes/home'; import About from '../routes/about'; // import Home from 'async!../routes/home'; // import Profile from 'async!../routes/profile'; if (module.hot) { require('preact/debug'); } export default class App extends Component { /** Gets fired when the route changes. * @param {Object} event "change" event from [preact-router](http://git.io/preact-router) * @param {string} event.url The newly routed URL */ handleRoute = e => { this.currentUrl = e.url; }; render() { return (
import('../routes/profile').then(m => m.default)} /> import('../routes/profile').then(m => m.default)} />
); } } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/components/header/index.js ================================================ import { h, Component } from 'preact'; import { Link } from 'preact-router/match'; import style from './style'; export default class Header extends Component { render() { return (

Preact App

); } } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/components/header/style.css ================================================ .header { position: fixed; left: 0; top: 0; width: 100%; height: 56px; padding: 0; background: #673AB7; box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); z-index: 50; } .header h1 { float: left; margin: 0; padding: 0 15px; font-size: 24px; line-height: 56px; font-weight: 400; color: #FFF; } .header nav { float: right; font-size: 100%; } .header nav a { display: inline-block; height: 56px; line-height: 56px; padding: 0 15px; min-width: 50px; text-align: center; background: rgba(255,255,255,0); text-decoration: none; color: #FFF; will-change: background-color; } .header nav a:hover, .header nav a:active { background: rgba(0,0,0,0.2); } .header nav a.active { background: rgba(0,0,0,0.4); } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/components/info.js ================================================ import { h, Component } from 'preact'; export default class Info extends Component { render() { return (

Info

This is the Info component.

); } } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/index.js ================================================ import './style'; import App from './components/app'; export default App; ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/manifest.json ================================================ { "name": "preact-app", "short_name": "preact-app", "start_url": "/", "display": "standalone", "orientation": "portrait", "background_color": "#fff", "theme_color": "#673ab8", "icons": [ { "src": "/assets/icons/android-chrome-192x192.png", "type": "image/png", "sizes": "192x192" }, { "src": "/assets/icons/android-chrome-512x512.png", "type": "image/png", "sizes": "512x512" } ] } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/routes/about/index.js ================================================ import { h, Component } from 'preact'; export default class About extends Component { render() { return (

About

This is the About component.

); } } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/routes/home/index.js ================================================ import { h, Component } from 'preact'; import style from './style'; export default class Home extends Component { render() { return (

Home

This is the Home component.

); } } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/routes/home/style.css ================================================ .home { padding: 56px 20px; min-height: 100%; width: 100%; } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/routes/profile/index.js ================================================ import { h, Component } from 'preact'; import style from './style'; export default class Profile extends Component { state = { time: Date.now(), count: 10 }; // gets called when this route is navigated to componentDidMount() { // start a timer for the clock: this.timer = setInterval(this.updateTime, 1000); } // gets called just before navigating away from the route componentWillUnmount() { clearInterval(this.timer); } // update the current time updateTime = () => { this.setState({ time: Date.now() }); }; increment = () => { this.setState({ count: this.state.count+1 }); }; // Note: `user` comes from the URL, courtesy of our router render({ user }, { time, count }) { return (

Profile: {user}

This is the user profile for a user named { user }.

Current time: {new Date(time).toLocaleString()}

{' '} Clicked {count} times.

); } } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/routes/profile/style.css ================================================ .profile { padding: 56px 20px; min-height: 100%; width: 100%; } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/style/index.css ================================================ html, body { height: 100%; width: 100%; padding: 0; margin: 0; background: #FAFAFA; font-family: 'Helvetica Neue', arial, sans-serif; font-weight: 400; color: #444; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } * { box-sizing: border-box; } #app { height: 100%; } ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/tests/__mocks__/browserMocks.js ================================================ // Mock Browser API's which are not supported by JSDOM, e.g. ServiceWorker, LocalStorage /** * An example how to mock localStorage is given below 👇 */ /* // Mocks localStorage const localStorageMock = (function() { let store = {}; return { getItem: (key) => store[key] || null, setItem: (key, value) => store[key] = value.toString(), clear: () => store = {} }; })(); Object.defineProperty(window, 'localStorage', { value: localStorageMock }); */ ================================================ FILE: packages/guess-parser/test/fixtures/preact-app/src/tests/header.test.js ================================================ import { h, Component } from 'preact'; import Header from '../components/header'; import { Link } from 'preact-router/match'; // See: https://github.com/mzgoddard/preact-render-spy import { shallow, deep } from 'preact-render-spy'; describe('Initial Test of the Header', () => { test('Header renders 3 nav items', () => { const context = shallow(
); expect(context.find('h1').text()).toBe('Preact App'); expect(context.find().length).toBe(3); }); }); ================================================ FILE: packages/guess-parser/test/fixtures/react-app/.gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # dependencies /node_modules # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: packages/guess-parser/test/fixtures/react-app/README.md ================================================ This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). Below you will find some information on how to perform common tasks.
You can find the most recent version of this guide [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md). ## Table of Contents - [Updating to New Releases](#updating-to-new-releases) - [Sending Feedback](#sending-feedback) - [Folder Structure](#folder-structure) - [Available Scripts](#available-scripts) - [npm start](#npm-start) - [npm test](#npm-test) - [npm run build](#npm-run-build) - [npm run eject](#npm-run-eject) - [Supported Browsers](#supported-browsers) - [Supported Language Features and Polyfills](#supported-language-features-and-polyfills) - [Syntax Highlighting in the Editor](#syntax-highlighting-in-the-editor) - [Displaying Lint Output in the Editor](#displaying-lint-output-in-the-editor) - [Debugging in the Editor](#debugging-in-the-editor) - [Formatting Code Automatically](#formatting-code-automatically) - [Changing the Page ``](#changing-the-page-title) - [Installing a Dependency](#installing-a-dependency) - [Importing a Component](#importing-a-component) - [Code Splitting](#code-splitting) - [Adding a Stylesheet](#adding-a-stylesheet) - [Post-Processing CSS](#post-processing-css) - [Adding a CSS Preprocessor (Sass, Less etc.)](#adding-a-css-preprocessor-sass-less-etc) - [Adding Images, Fonts, and Files](#adding-images-fonts-and-files) - [Using the `public` Folder](#using-the-public-folder) - [Changing the HTML](#changing-the-html) - [Adding Assets Outside of the Module System](#adding-assets-outside-of-the-module-system) - [When to Use the `public` Folder](#when-to-use-the-public-folder) - [Using Global Variables](#using-global-variables) - [Adding Bootstrap](#adding-bootstrap) - [Using a Custom Theme](#using-a-custom-theme) - [Adding Flow](#adding-flow) - [Adding a Router](#adding-a-router) - [Adding Custom Environment Variables](#adding-custom-environment-variables) - [Referencing Environment Variables in the HTML](#referencing-environment-variables-in-the-html) - [Adding Temporary Environment Variables In Your Shell](#adding-temporary-environment-variables-in-your-shell) - [Adding Development Environment Variables In `.env`](#adding-development-environment-variables-in-env) - [Can I Use Decorators?](#can-i-use-decorators) - [Fetching Data with AJAX Requests](#fetching-data-with-ajax-requests) - [Integrating with an API Backend](#integrating-with-an-api-backend) - [Node](#node) - [Ruby on Rails](#ruby-on-rails) - [Proxying API Requests in Development](#proxying-api-requests-in-development) - ["Invalid Host Header" Errors After Configuring Proxy](#invalid-host-header-errors-after-configuring-proxy) - [Configuring the Proxy Manually](#configuring-the-proxy-manually) - [Configuring a WebSocket Proxy](#configuring-a-websocket-proxy) - [Using HTTPS in Development](#using-https-in-development) - [Generating Dynamic `<meta>` Tags on the Server](#generating-dynamic-meta-tags-on-the-server) - [Pre-Rendering into Static HTML Files](#pre-rendering-into-static-html-files) - [Injecting Data from the Server into the Page](#injecting-data-from-the-server-into-the-page) - [Running Tests](#running-tests) - [Filename Conventions](#filename-conventions) - [Command Line Interface](#command-line-interface) - [Version Control Integration](#version-control-integration) - [Writing Tests](#writing-tests) - [Testing Components](#testing-components) - [Using Third Party Assertion Libraries](#using-third-party-assertion-libraries) - [Initializing Test Environment](#initializing-test-environment) - [Focusing and Excluding Tests](#focusing-and-excluding-tests) - [Coverage Reporting](#coverage-reporting) - [Continuous Integration](#continuous-integration) - [Disabling jsdom](#disabling-jsdom) - [Snapshot Testing](#snapshot-testing) - [Editor Integration](#editor-integration) - [Debugging Tests](#debugging-tests) - [Debugging Tests in Chrome](#debugging-tests-in-chrome) - [Debugging Tests in Visual Studio Code](#debugging-tests-in-visual-studio-code) - [Developing Components in Isolation](#developing-components-in-isolation) - [Getting Started with Storybook](#getting-started-with-storybook) - [Getting Started with Styleguidist](#getting-started-with-styleguidist) - [Publishing Components to npm](#publishing-components-to-npm) - [Making a Progressive Web App](#making-a-progressive-web-app) - [Opting Out of Caching](#opting-out-of-caching) - [Offline-First Considerations](#offline-first-considerations) - [Progressive Web App Metadata](#progressive-web-app-metadata) - [Analyzing the Bundle Size](#analyzing-the-bundle-size) - [Deployment](#deployment) - [Static Server](#static-server) - [Other Solutions](#other-solutions) - [Serving Apps with Client-Side Routing](#serving-apps-with-client-side-routing) - [Building for Relative Paths](#building-for-relative-paths) - [Azure](#azure) - [Firebase](#firebase) - [GitHub Pages](#github-pages) - [Heroku](#heroku) - [Netlify](#netlify) - [Now](#now) - [S3 and CloudFront](#s3-and-cloudfront) - [Surge](#surge) - [Advanced Configuration](#advanced-configuration) - [Troubleshooting](#troubleshooting) - [`npm start` doesn’t detect changes](#npm-start-doesnt-detect-changes) - [`npm test` hangs on macOS Sierra](#npm-test-hangs-on-macos-sierra) - [`npm run build` exits too early](#npm-run-build-exits-too-early) - [`npm run build` fails on Heroku](#npm-run-build-fails-on-heroku) - [`npm run build` fails to minify](#npm-run-build-fails-to-minify) - [Moment.js locales are missing](#momentjs-locales-are-missing) - [Alternatives to Ejecting](#alternatives-to-ejecting) - [Something Missing?](#something-missing) ## Updating to New Releases Create React App is divided into two packages: * `create-react-app` is a global command-line utility that you use to create new projects. * `react-scripts` is a development dependency in the generated projects (including this one). You almost never need to update `create-react-app` itself: it delegates all the setup to `react-scripts`. When you run `create-react-app`, it always creates the project with the latest version of `react-scripts` so you’ll get all the new features and improvements in newly created apps automatically. To update an existing project to a new version of `react-scripts`, [open the changelog](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md), find the version you’re currently on (check `package.json` in this folder if you’re not sure), and apply the migration instructions for the newer versions. In most cases bumping the `react-scripts` version in `package.json` and running `npm install` in this folder should be enough, but it’s good to consult the [changelog](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md) for potential breaking changes. We commit to keeping the breaking changes minimal so you can upgrade `react-scripts` painlessly. ## Sending Feedback We are always open to [your feedback](https://github.com/facebookincubator/create-react-app/issues). ## Folder Structure After creation, your project should look like this: ``` my-app/ README.md node_modules/ package.json public/ index.html favicon.ico src/ App.css App.js App.test.js index.css index.js logo.svg ``` For the project to build, **these files must exist with exact filenames**: * `public/index.html` is the page template; * `src/index.js` is the JavaScript entry point. You can delete or rename the other files. You may create subdirectories inside `src`. For faster rebuilds, only files inside `src` are processed by Webpack.<br> You need to **put any JS and CSS files inside `src`**, otherwise Webpack won’t see them. Only files inside `public` can be used from `public/index.html`.<br> Read instructions below for using assets from JavaScript and HTML. You can, however, create more top-level directories.<br> They will not be included in the production build so you can use them for things like documentation. ## Available Scripts In the project directory, you can run: ### `npm start` Runs the app in the development mode.<br> Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits.<br> You will also see any lint errors in the console. ### `npm test` Launches the test runner in the interactive watch mode.<br> See the section about [running tests](#running-tests) for more information. ### `npm run build` Builds the app for production to the `build` folder.<br> It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes.<br> Your app is ready to be deployed! See the section about [deployment](#deployment) for more information. ### `npm run eject` **Note: this is a one-way operation. Once you `eject`, you can’t go back!** If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. ## Supported Browsers By default, the generated project uses the latest version of React. You can refer [to the React documentation](https://reactjs.org/docs/react-dom.html#browser-support) for more information about supported browsers. ## Supported Language Features and Polyfills This project supports a superset of the latest JavaScript standard.<br> In addition to [ES6](https://github.com/lukehoban/es6features) syntax features, it also supports: * [Exponentiation Operator](https://github.com/rwaldron/exponentiation-operator) (ES2016). * [Async/await](https://github.com/tc39/ecmascript-asyncawait) (ES2017). * [Object Rest/Spread Properties](https://github.com/sebmarkbage/ecmascript-rest-spread) (stage 3 proposal). * [Dynamic import()](https://github.com/tc39/proposal-dynamic-import) (stage 3 proposal) * [Class Fields and Static Properties](https://github.com/tc39/proposal-class-public-fields) (part of stage 3 proposal). * [JSX](https://facebook.github.io/react/docs/introducing-jsx.html) and [Flow](https://flowtype.org/) syntax. Learn more about [different proposal stages](https://babeljs.io/docs/plugins/#presets-stage-x-experimental-presets-). While we recommend using experimental proposals with some caution, Facebook heavily uses these features in the product code, so we intend to provide [codemods](https://medium.com/@cpojer/effective-javascript-codemods-5a6686bb46fb) if any of these proposals change in the future. Note that **the project only includes a few ES6 [polyfills](https://en.wikipedia.org/wiki/Polyfill)**: * [`Object.assign()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) via [`object-assign`](https://github.com/sindresorhus/object-assign). * [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) via [`promise`](https://github.com/then/promise). * [`fetch()`](https://developer.mozilla.org/en/docs/Web/API/Fetch_API) via [`whatwg-fetch`](https://github.com/github/fetch). If you use any other ES6+ features that need **runtime support** (such as `Array.from()` or `Symbol`), make sure you are including the appropriate polyfills manually, or that the browsers you are targeting already support them. Also note that using some newer syntax features like `for...of` or `[...nonArrayValue]` causes Babel to emit code that depends on ES6 runtime features and might not work without a polyfill. When in doubt, use [Babel REPL](https://babeljs.io/repl/) to see what any specific syntax compiles down to. ## Syntax Highlighting in the Editor To configure the syntax highlighting in your favorite text editor, head to the [relevant Babel documentation page](https://babeljs.io/docs/editors) and follow the instructions. Some of the most popular editors are covered. ## Displaying Lint Output in the Editor >Note: this feature is available with `react-scripts@0.2.0` and higher.<br> >It also only works with npm 3 or higher. Some editors, including Sublime Text, Atom, and Visual Studio Code, provide plugins for ESLint. They are not required for linting. You should see the linter output right in your terminal as well as the browser console. However, if you prefer the lint results to appear right in your editor, there are some extra steps you can do. You would need to install an ESLint plugin for your editor first. Then, add a file called `.eslintrc` to the project root: ```js { "extends": "react-app" } ``` Now your editor should report the linting warnings. Note that even if you edit your `.eslintrc` file further, these changes will **only affect the editor integration**. They won’t affect the terminal and in-browser lint output. This is because Create React App intentionally provides a minimal set of rules that find common mistakes. If you want to enforce a coding style for your project, consider using [Prettier](https://github.com/jlongster/prettier) instead of ESLint style rules. ## Debugging in the Editor **This feature is currently only supported by [Visual Studio Code](https://code.visualstudio.com) and [WebStorm](https://www.jetbrains.com/webstorm/).** Visual Studio Code and WebStorm support debugging out of the box with Create React App. This enables you as a developer to write and debug your React code without leaving the editor, and most importantly it enables you to have a continuous development workflow, where context switching is minimal, as you don’t have to switch between tools. ### Visual Studio Code You would need to have the latest version of [VS Code](https://code.visualstudio.com) and VS Code [Chrome Debugger Extension](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) installed. Then add the block below to your `launch.json` file and put it inside the `.vscode` folder in your app’s root directory. ```json { "version": "0.2.0", "configurations": [{ "name": "Chrome", "type": "chrome", "request": "launch", "url": "http://localhost:3000", "webRoot": "${workspaceRoot}/src", "sourceMapPathOverrides": { "webpack:///src/*": "${webRoot}/*" } }] } ``` >Note: the URL may be different if you've made adjustments via the [HOST or PORT environment variables](#advanced-configuration). Start your app by running `npm start`, and start debugging in VS Code by pressing `F5` or by clicking the green debug icon. You can now write code, set breakpoints, make changes to the code, and debug your newly modified code—all from your editor. Having problems with VS Code Debugging? Please see their [troubleshooting guide](https://github.com/Microsoft/vscode-chrome-debug/blob/master/README.md#troubleshooting). ### WebStorm You would need to have [WebStorm](https://www.jetbrains.com/webstorm/) and [JetBrains IDE Support](https://chrome.google.com/webstore/detail/jetbrains-ide-support/hmhgeddbohgjknpmjagkdomcpobmllji) Chrome extension installed. In the WebStorm menu `Run` select `Edit Configurations...`. Then click `+` and select `JavaScript Debug`. Paste `http://localhost:3000` into the URL field and save the configuration. >Note: the URL may be different if you've made adjustments via the [HOST or PORT environment variables](#advanced-configuration). Start your app by running `npm start`, then press `^D` on macOS or `F9` on Windows and Linux or click the green debug icon to start debugging in WebStorm. The same way you can debug your application in IntelliJ IDEA Ultimate, PhpStorm, PyCharm Pro, and RubyMine. ## Formatting Code Automatically Prettier is an opinionated code formatter with support for JavaScript, CSS and JSON. With Prettier you can format the code you write automatically to ensure a code style within your project. See the [Prettier's GitHub page](https://github.com/prettier/prettier) for more information, and look at this [page to see it in action](https://prettier.github.io/prettier/). To format our code whenever we make a commit in git, we need to install the following dependencies: ```sh npm install --save husky lint-staged prettier ``` Alternatively you may use `yarn`: ```sh yarn add husky lint-staged prettier ``` * `husky` makes it easy to use githooks as if they are npm scripts. * `lint-staged` allows us to run scripts on staged files in git. See this [blog post about lint-staged to learn more about it](https://medium.com/@okonetchnikov/make-linting-great-again-f3890e1ad6b8). * `prettier` is the JavaScript formatter we will run before commits. Now we can make sure every file is formatted correctly by adding a few lines to the `package.json` in the project root. Add the following line to `scripts` section: ```diff "scripts": { + "precommit": "lint-staged", "start": "react-scripts start", "build": "react-scripts build", ``` Next we add a 'lint-staged' field to the `package.json`, for example: ```diff "dependencies": { // ... }, + "lint-staged": { + "src/**/*.{js,jsx,json,css}": [ + "prettier --single-quote --write", + "git add" + ] + }, "scripts": { ``` Now, whenever you make a commit, Prettier will format the changed files automatically. You can also run `./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx,json,css}"` to format your entire project for the first time. Next you might want to integrate Prettier in your favorite editor. Read the section on [Editor Integration](https://prettier.io/docs/en/editors.html) on the Prettier GitHub page. ## Changing the Page `<title>` You can find the source HTML file in the `public` folder of the generated project. You may edit the `<title>` tag in it to change the title from “React App” to anything else. Note that normally you wouldn’t edit files in the `public` folder very often. For example, [adding a stylesheet](#adding-a-stylesheet) is done without touching the HTML. If you need to dynamically update the page title based on the content, you can use the browser [`document.title`](https://developer.mozilla.org/en-US/docs/Web/API/Document/title) API. For more complex scenarios when you want to change the title from React components, you can use [React Helmet](https://github.com/nfl/react-helmet), a third party library. If you use a custom server for your app in production and want to modify the title before it gets sent to the browser, you can follow advice in [this section](#generating-dynamic-meta-tags-on-the-server). Alternatively, you can pre-build each page as a static HTML file which then loads the JavaScript bundle, which is covered [here](#pre-rendering-into-static-html-files). ## Installing a Dependency The generated project includes React and ReactDOM as dependencies. It also includes a set of scripts used by Create React App as a development dependency. You may install other dependencies (for example, React Router) with `npm`: ```sh npm install --save react-router ``` Alternatively you may use `yarn`: ```sh yarn add react-router ``` This works for any library, not just `react-router`. ## Importing a Component This project setup supports ES6 modules thanks to Babel.<br> While you can still use `require()` and `module.exports`, we encourage you to use [`import` and `export`](http://exploringjs.com/es6/ch_modules.html) instead. For example: ### `Button.js` ```js import React, { Component } from 'react'; class Button extends Component { render() { // ... } } export default Button; // Don’t forget to use export default! ``` ### `DangerButton.js` ```js import React, { Component } from 'react'; import Button from './Button'; // Import a component from another file class DangerButton extends Component { render() { return <Button color="red" />; } } export default DangerButton; ``` Be aware of the [difference between default and named exports](http://stackoverflow.com/questions/36795819/react-native-es-6-when-should-i-use-curly-braces-for-import/36796281#36796281). It is a common source of mistakes. We suggest that you stick to using default imports and exports when a module only exports a single thing (for example, a component). That’s what you get when you use `export default Button` and `import Button from './Button'`. Named exports are useful for utility modules that export several functions. A module may have at most one default export and as many named exports as you like. Learn more about ES6 modules: * [When to use the curly braces?](http://stackoverflow.com/questions/36795819/react-native-es-6-when-should-i-use-curly-braces-for-import/36796281#36796281) * [Exploring ES6: Modules](http://exploringjs.com/es6/ch_modules.html) * [Understanding ES6: Modules](https://leanpub.com/understandinges6/read#leanpub-auto-encapsulating-code-with-modules) ## Code Splitting Instead of downloading the entire app before users can use it, code splitting allows you to split your code into small chunks which you can then load on demand. This project setup supports code splitting via [dynamic `import()`](http://2ality.com/2017/01/import-operator.html#loading-code-on-demand). Its [proposal](https://github.com/tc39/proposal-dynamic-import) is in stage 3. The `import()` function-like form takes the module name as an argument and returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which always resolves to the namespace object of the module. Here is an example: ### `moduleA.js` ```js const moduleA = 'Hello'; export { moduleA }; ``` ### `App.js` ```js import React, { Component } from 'react'; class App extends Component { handleClick = () => { import('./moduleA') .then(({ moduleA }) => { // Use moduleA }) .catch(err => { // Handle failure }); }; render() { return ( <div> <button onClick={this.handleClick}>Load</button> </div> ); } } export default App; ``` This will make `moduleA.js` and all its unique dependencies as a separate chunk that only loads after the user clicks the 'Load' button. You can also use it with `async` / `await` syntax if you prefer it. ### With React Router If you are using React Router check out [this tutorial](http://serverless-stack.com/chapters/code-splitting-in-create-react-app.html) on how to use code splitting with it. You can find the companion GitHub repository [here](https://github.com/AnomalyInnovations/serverless-stack-demo-client/tree/code-splitting-in-create-react-app). Also check out the [Code Splitting](https://reactjs.org/docs/code-splitting.html) section in React documentation. ## Adding a Stylesheet This project setup uses [Webpack](https://webpack.js.org/) for handling all assets. Webpack offers a custom way of “extending” the concept of `import` beyond JavaScript. To express that a JavaScript file depends on a CSS file, you need to **import the CSS from the JavaScript file**: ### `Button.css` ```css .Button { padding: 20px; } ``` ### `Button.js` ```js import React, { Component } from 'react'; import './Button.css'; // Tell Webpack that Button.js uses these styles class Button extends Component { render() { // You can use them as regular CSS styles return <div className="Button" />; } } ``` **This is not required for React** but many people find this feature convenient. You can read about the benefits of this approach [here](https://medium.com/seek-ui-engineering/block-element-modifying-your-javascript-components-d7f99fcab52b). However you should be aware that this makes your code less portable to other build tools and environments than Webpack. In development, expressing dependencies this way allows your styles to be reloaded on the fly as you edit them. In production, all CSS files will be concatenated into a single minified `.css` file in the build output. If you are concerned about using Webpack-specific semantics, you can put all your CSS right into `src/index.css`. It would still be imported from `src/index.js`, but you could always remove that import if you later migrate to a different build tool. ## Post-Processing CSS This project setup minifies your CSS and adds vendor prefixes to it automatically through [Autoprefixer](https://github.com/postcss/autoprefixer) so you don’t need to worry about it. For example, this: ```css .App { display: flex; flex-direction: row; align-items: center; } ``` becomes this: ```css .App { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: horizontal; -webkit-box-direction: normal; -ms-flex-direction: row; flex-direction: row; -webkit-box-align: center; -ms-flex-align: center; align-items: center; } ``` If you need to disable autoprefixing for some reason, [follow this section](https://github.com/postcss/autoprefixer#disabling). ## Adding a CSS Preprocessor (Sass, Less etc.) Generally, we recommend that you don’t reuse the same CSS classes across different components. For example, instead of using a `.Button` CSS class in `<AcceptButton>` and `<RejectButton>` components, we recommend creating a `<Button>` component with its own `.Button` styles, that both `<AcceptButton>` and `<RejectButton>` can render (but [not inherit](https://facebook.github.io/react/docs/composition-vs-inheritance.html)). Following this rule often makes CSS preprocessors less useful, as features like mixins and nesting are replaced by component composition. You can, however, integrate a CSS preprocessor if you find it valuable. In this walkthrough, we will be using Sass, but you can also use Less, or another alternative. First, let’s install the command-line interface for Sass: ```sh npm install --save node-sass-chokidar ``` Alternatively you may use `yarn`: ```sh yarn add node-sass-chokidar ``` Then in `package.json`, add the following lines to `scripts`: ```diff "scripts": { + "build-css": "node-sass-chokidar src/ -o src/", + "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive", "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", ``` >Note: To use a different preprocessor, replace `build-css` and `watch-css` commands according to your preprocessor’s documentation. Now you can rename `src/App.css` to `src/App.scss` and run `npm run watch-css`. The watcher will find every Sass file in `src` subdirectories, and create a corresponding CSS file next to it, in our case overwriting `src/App.css`. Since `src/App.js` still imports `src/App.css`, the styles become a part of your application. You can now edit `src/App.scss`, and `src/App.css` will be regenerated. To share variables between Sass files, you can use Sass imports. For example, `src/App.scss` and other component style files could include `@import "./shared.scss";` with variable definitions. To enable importing files without using relative paths, you can add the `--include-path` option to the command in `package.json`. ``` "build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/", "watch-css": "npm run build-css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive", ``` This will allow you to do imports like ```scss @import 'styles/_colors.scss'; // assuming a styles directory under src/ @import 'nprogress/nprogress'; // importing a css file from the nprogress node module ``` At this point you might want to remove all CSS files from the source control, and add `src/**/*.css` to your `.gitignore` file. It is generally a good practice to keep the build products outside of the source control. As a final step, you may find it convenient to run `watch-css` automatically with `npm start`, and run `build-css` as a part of `npm run build`. You can use the `&&` operator to execute two scripts sequentially. However, there is no cross-platform way to run two scripts in parallel, so we will install a package for this: ```sh npm install --save npm-run-all ``` Alternatively you may use `yarn`: ```sh yarn add npm-run-all ``` Then we can change `start` and `build` scripts to include the CSS preprocessor commands: ```diff "scripts": { "build-css": "node-sass-chokidar src/ -o src/", "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive", - "start": "react-scripts start", - "build": "react-scripts build", + "start-js": "react-scripts start", + "start": "npm-run-all -p watch-css start-js", + "build-js": "react-scripts build", + "build": "npm-run-all build-css build-js", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" } ``` Now running `npm start` and `npm run build` also builds Sass files. **Why `node-sass-chokidar`?** `node-sass` has been reported as having the following issues: - `node-sass --watch` has been reported to have *performance issues* in certain conditions when used in a virtual machine or with docker. - Infinite styles compiling [#1939](https://github.com/facebookincubator/create-react-app/issues/1939) - `node-sass` has been reported as having issues with detecting new files in a directory [#1891](https://github.com/sass/node-sass/issues/1891) `node-sass-chokidar` is used here as it addresses these issues. ## Adding Images, Fonts, and Files With Webpack, using static assets like images and fonts works similarly to CSS. You can **`import` a file right in a JavaScript module**. This tells Webpack to include that file in the bundle. Unlike CSS imports, importing a file gives you a string value. This value is the final path you can reference in your code, e.g. as the `src` attribute of an image or the `href` of a link to a PDF. To reduce the number of requests to the server, importing images that are less than 10,000 bytes returns a [data URI](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) instead of a path. This applies to the following file extensions: bmp, gif, jpg, jpeg, and png. SVG files are excluded due to [#1153](https://github.com/facebookincubator/create-react-app/issues/1153). Here is an example: ```js import React from 'react'; import logo from './logo.png'; // Tell Webpack this JS file uses this image console.log(logo); // /logo.84287d09.png function Header() { // Import result is the URL of your image return <img src={logo} alt="Logo" />; } export default Header; ``` This ensures that when the project is built, Webpack will correctly move the images into the build folder, and provide us with correct paths. This works in CSS too: ```css .Logo { background-image: url(./logo.png); } ``` Webpack finds all relative module references in CSS (they start with `./`) and replaces them with the final paths from the compiled bundle. If you make a typo or accidentally delete an important file, you will see a compilation error, just like when you import a non-existent JavaScript module. The final filenames in the compiled bundle are generated by Webpack from content hashes. If the file content changes in the future, Webpack will give it a different name in production so you don’t need to worry about long-term caching of assets. Please be advised that this is also a custom feature of Webpack. **It is not required for React** but many people enjoy it (and React Native uses a similar mechanism for images).<br> An alternative way of handling static assets is described in the next section. ## Using the `public` Folder >Note: this feature is available with `react-scripts@0.5.0` and higher. ### Changing the HTML The `public` folder contains the HTML file so you can tweak it, for example, to [set the page title](#changing-the-page-title). The `<script>` tag with the compiled code will be added to it automatically during the build process. ### Adding Assets Outside of the Module System You can also add other assets to the `public` folder. Note that we normally encourage you to `import` assets in JavaScript files instead. For example, see the sections on [adding a stylesheet](#adding-a-stylesheet) and [adding images and fonts](#adding-images-fonts-and-files). This mechanism provides a number of benefits: * Scripts and stylesheets get minified and bundled together to avoid extra network requests. * Missing files cause compilation errors instead of 404 errors for your users. * Result filenames include content hashes so you don’t need to worry about browsers caching their old versions. However there is an **escape hatch** that you can use to add an asset outside of the module system. If you put a file into the `public` folder, it will **not** be processed by Webpack. Instead it will be copied into the build folder untouched. To reference assets in the `public` folder, you need to use a special variable called `PUBLIC_URL`. Inside `index.html`, you can use it like this: ```html <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> ``` Only files inside the `public` folder will be accessible by `%PUBLIC_URL%` prefix. If you need to use a file from `src` or `node_modules`, you’ll have to copy it there to explicitly specify your intention to make this file a part of the build. When you run `npm run build`, Create React App will substitute `%PUBLIC_URL%` with a correct absolute path so your project works even if you use client-side routing or host it at a non-root URL. In JavaScript code, you can use `process.env.PUBLIC_URL` for similar purposes: ```js render() { // Note: this is an escape hatch and should be used sparingly! // Normally we recommend using `import` for getting asset URLs // as described in “Adding Images and Fonts” above this section. return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />; } ``` Keep in mind the downsides of this approach: * None of the files in `public` folder get post-processed or minified. * Missing files will not be called at compilation time, and will cause 404 errors for your users. * Result filenames won’t include content hashes so you’ll need to add query arguments or rename them every time they change. ### When to Use the `public` Folder Normally we recommend importing [stylesheets](#adding-a-stylesheet), [images, and fonts](#adding-images-fonts-and-files) from JavaScript. The `public` folder is useful as a workaround for a number of less common cases: * You need a file with a specific name in the build output, such as [`manifest.webmanifest`](https://developer.mozilla.org/en-US/docs/Web/Manifest). * You have thousands of images and need to dynamically reference their paths. * You want to include a small script like [`pace.js`](http://github.hubspot.com/pace/docs/welcome/) outside of the bundled code. * Some library may be incompatible with Webpack and you have no other option but to include it as a `<script>` tag. Note that if you add a `<script>` that declares global variables, you also need to read the next section on using them. ## Using Global Variables When you include a script in the HTML file that defines global variables and try to use one of these variables in the code, the linter will complain because it cannot see the definition of the variable. You can avoid this by reading the global variable explicitly from the `window` object, for example: ```js const $ = window.$; ``` This makes it obvious you are using a global variable intentionally rather than because of a typo. Alternatively, you can force the linter to ignore any line by adding `// eslint-disable-line` after it. ## Adding Bootstrap You don’t have to use [React Bootstrap](https://react-bootstrap.github.io) together with React but it is a popular library for integrating Bootstrap with React apps. If you need it, you can integrate it with Create React App by following these steps: Install React Bootstrap and Bootstrap from npm. React Bootstrap does not include Bootstrap CSS so this needs to be installed as well: ```sh npm install --save react-bootstrap bootstrap@3 ``` Alternatively you may use `yarn`: ```sh yarn add react-bootstrap bootstrap@3 ``` Import Bootstrap CSS and optionally Bootstrap theme CSS in the beginning of your ```src/index.js``` file: ```js import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/css/bootstrap-theme.css'; // Put any other imports below so that CSS from your // components takes precedence over default styles. ``` Import required React Bootstrap components within ```src/App.js``` file or your custom component files: ```js import { Navbar, Jumbotron, Button } from 'react-bootstrap'; ``` Now you are ready to use the imported React Bootstrap components within your component hierarchy defined in the render method. Here is an example [`App.js`](https://gist.githubusercontent.com/gaearon/85d8c067f6af1e56277c82d19fd4da7b/raw/6158dd991b67284e9fc8d70b9d973efe87659d72/App.js) redone using React Bootstrap. ### Using a Custom Theme Sometimes you might need to tweak the visual styles of Bootstrap (or equivalent package).<br> We suggest the following approach: * Create a new package that depends on the package you wish to customize, e.g. Bootstrap. * Add the necessary build steps to tweak the theme, and publish your package on npm. * Install your own theme npm package as a dependency of your app. Here is an example of adding a [customized Bootstrap](https://medium.com/@tacomanator/customizing-create-react-app-aa9ffb88165) that follows these steps. ## Adding Flow Flow is a static type checker that helps you write code with fewer bugs. Check out this [introduction to using static types in JavaScript](https://medium.com/@preethikasireddy/why-use-static-types-in-javascript-part-1-8382da1e0adb) if you are new to this concept. Recent versions of [Flow](http://flowtype.org/) work with Create React App projects out of the box. To add Flow to a Create React App project, follow these steps: 1. Run `npm install --save flow-bin` (or `yarn add flow-bin`). 2. Add `"flow": "flow"` to the `scripts` section of your `package.json`. 3. Run `npm run flow init` (or `yarn flow init`) to create a [`.flowconfig` file](https://flowtype.org/docs/advanced-configuration.html) in the root directory. 4. Add `// @flow` to any files you want to type check (for example, to `src/App.js`). Now you can run `npm run flow` (or `yarn flow`) to check the files for type errors. You can optionally use an IDE like [Nuclide](https://nuclide.io/docs/languages/flow/) for a better integrated experience. In the future we plan to integrate it into Create React App even more closely. To learn more about Flow, check out [its documentation](https://flowtype.org/). ## Adding a Router Create React App doesn't prescribe a specific routing solution, but [React Router](https://reacttraining.com/react-router/) is the most popular one. To add it, run: ```sh npm install --save react-router-dom ``` Alternatively you may use `yarn`: ```sh yarn add react-router-dom ``` To try it, delete all the code in `src/App.js` and replace it with any of the examples on its website. The [Basic Example](https://reacttraining.com/react-router/web/example/basic) is a good place to get started. Note that [you may need to configure your production server to support client-side routing](#serving-apps-with-client-side-routing) before deploying your app. ## Adding Custom Environment Variables >Note: this feature is available with `react-scripts@0.2.3` and higher. Your project can consume variables declared in your environment as if they were declared locally in your JS files. By default you will have `NODE_ENV` defined for you, and any other environment variables starting with `REACT_APP_`. **The environment variables are embedded during the build time**. Since Create React App produces a static HTML/CSS/JS bundle, it can’t possibly read them at runtime. To read them at runtime, you would need to load HTML into memory on the server and replace placeholders in runtime, just like [described here](#injecting-data-from-the-server-into-the-page). Alternatively you can rebuild the app on the server anytime you change them. >Note: You must create custom environment variables beginning with `REACT_APP_`. Any other variables except `NODE_ENV` will be ignored to avoid accidentally [exposing a private key on the machine that could have the same name](https://github.com/facebookincubator/create-react-app/issues/865#issuecomment-252199527). Changing any environment variables will require you to restart the development server if it is running. These environment variables will be defined for you on `process.env`. For example, having an environment variable named `REACT_APP_SECRET_CODE` will be exposed in your JS as `process.env.REACT_APP_SECRET_CODE`. There is also a special built-in environment variable called `NODE_ENV`. You can read it from `process.env.NODE_ENV`. When you run `npm start`, it is always equal to `'development'`, when you run `npm test` it is always equal to `'test'`, and when you run `npm run build` to make a production bundle, it is always equal to `'production'`. **You cannot override `NODE_ENV` manually.** This prevents developers from accidentally deploying a slow development build to production. These environment variables can be useful for displaying information conditionally based on where the project is deployed or consuming sensitive data that lives outside of version control. First, you need to have environment variables defined. For example, let’s say you wanted to consume a secret defined in the environment inside a `<form>`: ```jsx render() { return ( <div> <small>You are running this application in <b>{process.env.NODE_ENV}</b> mode.</small> <form> <input type="hidden" defaultValue={process.env.REACT_APP_SECRET_CODE} /> </form> </div> ); } ``` During the build, `process.env.REACT_APP_SECRET_CODE` will be replaced with the current value of the `REACT_APP_SECRET_CODE` environment variable. Remember that the `NODE_ENV` variable will be set for you automatically. When you load the app in the browser and inspect the `<input>`, you will see its value set to `abcdef`, and the bold text will show the environment provided when using `npm start`: ```html <div> <small>You are running this application in <b>development</b> mode.</small> <form> <input type="hidden" value="abcdef" /> </form> </div> ``` The above form is looking for a variable called `REACT_APP_SECRET_CODE` from the environment. In order to consume this value, we need to have it defined in the environment. This can be done using two ways: either in your shell or in a `.env` file. Both of these ways are described in the next few sections. Having access to the `NODE_ENV` is also useful for performing actions conditionally: ```js if (process.env.NODE_ENV !== 'production') { analytics.disable(); } ``` When you compile the app with `npm run build`, the minification step will strip out this condition, and the resulting bundle will be smaller. ### Referencing Environment Variables in the HTML >Note: this feature is available with `react-scripts@0.9.0` and higher. You can also access the environment variables starting with `REACT_APP_` in the `public/index.html`. For example: ```html <title>%REACT_APP_WEBSITE_NAME% ``` Note that the caveats from the above section apply: * Apart from a few built-in variables (`NODE_ENV` and `PUBLIC_URL`), variable names must start with `REACT_APP_` to work. * The environment variables are injected at build time. If you need to inject them at runtime, [follow this approach instead](#generating-dynamic-meta-tags-on-the-server). ### Adding Temporary Environment Variables In Your Shell Defining environment variables can vary between OSes. It’s also important to know that this manner is temporary for the life of the shell session. #### Windows (cmd.exe) ```cmd set "REACT_APP_SECRET_CODE=abcdef" && npm start ``` (Note: Quotes around the variable assignment are required to avoid a trailing whitespace.) #### Windows (Powershell) ```Powershell ($env:REACT_APP_SECRET_CODE = "abcdef") -and (npm start) ``` #### Linux, macOS (Bash) ```bash REACT_APP_SECRET_CODE=abcdef npm start ``` ### Adding Development Environment Variables In `.env` >Note: this feature is available with `react-scripts@0.5.0` and higher. To define permanent environment variables, create a file called `.env` in the root of your project: ``` REACT_APP_SECRET_CODE=abcdef ``` >Note: You must create custom environment variables beginning with `REACT_APP_`. Any other variables except `NODE_ENV` will be ignored to avoid [accidentally exposing a private key on the machine that could have the same name](https://github.com/facebookincubator/create-react-app/issues/865#issuecomment-252199527). Changing any environment variables will require you to restart the development server if it is running. `.env` files **should be** checked into source control (with the exclusion of `.env*.local`). #### What other `.env` files can be used? >Note: this feature is **available with `react-scripts@1.0.0` and higher**. * `.env`: Default. * `.env.local`: Local overrides. **This file is loaded for all environments except test.** * `.env.development`, `.env.test`, `.env.production`: Environment-specific settings. * `.env.development.local`, `.env.test.local`, `.env.production.local`: Local overrides of environment-specific settings. Files on the left have more priority than files on the right: * `npm start`: `.env.development.local`, `.env.development`, `.env.local`, `.env` * `npm run build`: `.env.production.local`, `.env.production`, `.env.local`, `.env` * `npm test`: `.env.test.local`, `.env.test`, `.env` (note `.env.local` is missing) These variables will act as the defaults if the machine does not explicitly set them.
Please refer to the [dotenv documentation](https://github.com/motdotla/dotenv) for more details. >Note: If you are defining environment variables for development, your CI and/or hosting platform will most likely need these defined as well. Consult their documentation how to do this. For example, see the documentation for [Travis CI](https://docs.travis-ci.com/user/environment-variables/) or [Heroku](https://devcenter.heroku.com/articles/config-vars). #### Expanding Environment Variables In `.env` >Note: this feature is available with `react-scripts@1.1.0` and higher. Expand variables already on your machine for use in your `.env` file (using [dotenv-expand](https://github.com/motdotla/dotenv-expand)). For example, to get the environment variable `npm_package_version`: ``` REACT_APP_VERSION=$npm_package_version # also works: # REACT_APP_VERSION=${npm_package_version} ``` Or expand variables local to the current `.env` file: ``` DOMAIN=www.example.com REACT_APP_FOO=$DOMAIN/foo REACT_APP_BAR=$DOMAIN/bar ``` ## Can I Use Decorators? Many popular libraries use [decorators](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841) in their documentation.
Create React App doesn’t support decorator syntax at the moment because: * It is an experimental proposal and is subject to change. * The current specification version is not officially supported by Babel. * If the specification changes, we won’t be able to write a codemod because we don’t use them internally at Facebook. However in many cases you can rewrite decorator-based code without decorators just as fine.
Please refer to these two threads for reference: * [#214](https://github.com/facebookincubator/create-react-app/issues/214) * [#411](https://github.com/facebookincubator/create-react-app/issues/411) Create React App will add decorator support when the specification advances to a stable stage. ## Fetching Data with AJAX Requests React doesn't prescribe a specific approach to data fetching, but people commonly use either a library like [axios](https://github.com/axios/axios) or the [`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) provided by the browser. Conveniently, Create React App includes a polyfill for `fetch()` so you can use it without worrying about the browser support. The global `fetch` function allows to easily makes AJAX requests. It takes in a URL as an input and returns a `Promise` that resolves to a `Response` object. You can find more information about `fetch` [here](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). This project also includes a [Promise polyfill](https://github.com/then/promise) which provides a full implementation of Promises/A+. A Promise represents the eventual result of an asynchronous operation, you can find more information about Promises [here](https://www.promisejs.org/) and [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). Both axios and `fetch()` use Promises under the hood. You can also use the [`async / await`](https://davidwalsh.name/async-await) syntax to reduce the callback nesting. You can learn more about making AJAX requests from React components in [the FAQ entry on the React website](https://reactjs.org/docs/faq-ajax.html). ## Integrating with an API Backend These tutorials will help you to integrate your app with an API backend running on another port, using `fetch()` to access it. ### Node Check out [this tutorial](https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/). You can find the companion GitHub repository [here](https://github.com/fullstackreact/food-lookup-demo). ### Ruby on Rails Check out [this tutorial](https://www.fullstackreact.com/articles/how-to-get-create-react-app-to-work-with-your-rails-api/). You can find the companion GitHub repository [here](https://github.com/fullstackreact/food-lookup-demo-rails). ## Proxying API Requests in Development >Note: this feature is available with `react-scripts@0.2.3` and higher. People often serve the front-end React app from the same host and port as their backend implementation.
For example, a production setup might look like this after the app is deployed: ``` / - static server returns index.html with React app /todos - static server returns index.html with React app /api/todos - server handles any /api/* requests using the backend implementation ``` Such setup is **not** required. However, if you **do** have a setup like this, it is convenient to write requests like `fetch('/api/todos')` without worrying about redirecting them to another host or port during development. To tell the development server to proxy any unknown requests to your API server in development, add a `proxy` field to your `package.json`, for example: ```js "proxy": "http://localhost:4000", ``` This way, when you `fetch('/api/todos')` in development, the development server will recognize that it’s not a static asset, and will proxy your request to `http://localhost:4000/api/todos` as a fallback. The development server will **only** attempt to send requests without `text/html` in its `Accept` header to the proxy. Conveniently, this avoids [CORS issues](http://stackoverflow.com/questions/21854516/understanding-ajax-cors-and-security-considerations) and error messages like this in development: ``` Fetch API cannot load http://localhost:4000/api/todos. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. ``` Keep in mind that `proxy` only has effect in development (with `npm start`), and it is up to you to ensure that URLs like `/api/todos` point to the right thing in production. You don’t have to use the `/api` prefix. Any unrecognized request without a `text/html` accept header will be redirected to the specified `proxy`. The `proxy` option supports HTTP, HTTPS and WebSocket connections.
If the `proxy` option is **not** flexible enough for you, alternatively you can: * [Configure the proxy yourself](#configuring-the-proxy-manually) * Enable CORS on your server ([here’s how to do it for Express](http://enable-cors.org/server_expressjs.html)). * Use [environment variables](#adding-custom-environment-variables) to inject the right server host and port into your app. ### "Invalid Host Header" Errors After Configuring Proxy When you enable the `proxy` option, you opt into a more strict set of host checks. This is necessary because leaving the backend open to remote hosts makes your computer vulnerable to DNS rebinding attacks. The issue is explained in [this article](https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a) and [this issue](https://github.com/webpack/webpack-dev-server/issues/887). This shouldn’t affect you when developing on `localhost`, but if you develop remotely like [described here](https://github.com/facebookincubator/create-react-app/issues/2271), you will see this error in the browser after enabling the `proxy` option: >Invalid Host header To work around it, you can specify your public development host in a file called `.env.development` in the root of your project: ``` HOST=mypublicdevhost.com ``` If you restart the development server now and load the app from the specified host, it should work. If you are still having issues or if you’re using a more exotic environment like a cloud editor, you can bypass the host check completely by adding a line to `.env.development.local`. **Note that this is dangerous and exposes your machine to remote code execution from malicious websites:** ``` # NOTE: THIS IS DANGEROUS! # It exposes your machine to attacks from the websites you visit. DANGEROUSLY_DISABLE_HOST_CHECK=true ``` We don’t recommend this approach. ### Configuring the Proxy Manually >Note: this feature is available with `react-scripts@1.0.0` and higher. If the `proxy` option is **not** flexible enough for you, you can specify an object in the following form (in `package.json`).
You may also specify any configuration value [`http-proxy-middleware`](https://github.com/chimurai/http-proxy-middleware#options) or [`http-proxy`](https://github.com/nodejitsu/node-http-proxy#options) supports. ```js { // ... "proxy": { "/api": { "target": "", "ws": true // ... } } // ... } ``` All requests matching this path will be proxies, no exceptions. This includes requests for `text/html`, which the standard `proxy` option does not proxy. If you need to specify multiple proxies, you may do so by specifying additional entries. Matches are regular expressions, so that you can use a regexp to match multiple paths. ```js { // ... "proxy": { // Matches any request starting with /api "/api": { "target": "", "ws": true // ... }, // Matches any request starting with /foo "/foo": { "target": "", "ssl": true, "pathRewrite": { "^/foo": "/foo/beta" } // ... }, // Matches /bar/abc.html but not /bar/sub/def.html "/bar/[^/]*[.]html": { "target": "", // ... }, // Matches /baz/abc.html and /baz/sub/def.html "/baz/.*/.*[.]html": { "target": "" // ... } } // ... } ``` ### Configuring a WebSocket Proxy When setting up a WebSocket proxy, there are a some extra considerations to be aware of. If you’re using a WebSocket engine like [Socket.io](https://socket.io/), you must have a Socket.io server running that you can use as the proxy target. Socket.io will not work with a standard WebSocket server. Specifically, don't expect Socket.io to work with [the websocket.org echo test](http://websocket.org/echo.html). There’s some good documentation available for [setting up a Socket.io server](https://socket.io/docs/). Standard WebSockets **will** work with a standard WebSocket server as well as the websocket.org echo test. You can use libraries like [ws](https://github.com/websockets/ws) for the server, with [native WebSockets in the browser](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket). Either way, you can proxy WebSocket requests manually in `package.json`: ```js { // ... "proxy": { "/socket": { // Your compatible WebSocket server "target": "ws://", // Tell http-proxy-middleware that this is a WebSocket proxy. // Also allows you to proxy WebSocket requests without an additional HTTP request // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade "ws": true // ... } } // ... } ``` ## Using HTTPS in Development >Note: this feature is available with `react-scripts@0.4.0` and higher. You may require the dev server to serve pages over HTTPS. One particular case where this could be useful is when using [the "proxy" feature](#proxying-api-requests-in-development) to proxy requests to an API server when that API server is itself serving HTTPS. To do this, set the `HTTPS` environment variable to `true`, then start the dev server as usual with `npm start`: #### Windows (cmd.exe) ```cmd set HTTPS=true&&npm start ``` #### Windows (Powershell) ```Powershell ($env:HTTPS = $true) -and (npm start) ``` (Note: the lack of whitespace is intentional.) #### Linux, macOS (Bash) ```bash HTTPS=true npm start ``` Note that the server will use a self-signed certificate, so your web browser will almost definitely display a warning upon accessing the page. ## Generating Dynamic `` Tags on the Server Since Create React App doesn’t support server rendering, you might be wondering how to make `` tags dynamic and reflect the current URL. To solve this, we recommend to add placeholders into the HTML, like this: ```html ``` Then, on the server, regardless of the backend you use, you can read `index.html` into memory and replace `__OG_TITLE__`, `__OG_DESCRIPTION__`, and any other placeholders with values depending on the current URL. Just make sure to sanitize and escape the interpolated values so that they are safe to embed into HTML! If you use a Node server, you can even share the route matching logic between the client and the server. However duplicating it also works fine in simple cases. ## Pre-Rendering into Static HTML Files If you’re hosting your `build` with a static hosting provider you can use [react-snapshot](https://www.npmjs.com/package/react-snapshot) or [react-snap](https://github.com/stereobooster/react-snap) to generate HTML pages for each route, or relative link, in your application. These pages will then seamlessly become active, or “hydrated”, when the JavaScript bundle has loaded. There are also opportunities to use this outside of static hosting, to take the pressure off the server when generating and caching routes. The primary benefit of pre-rendering is that you get the core content of each page _with_ the HTML payload—regardless of whether or not your JavaScript bundle successfully downloads. It also increases the likelihood that each route of your application will be picked up by search engines. You can read more about [zero-configuration pre-rendering (also called snapshotting) here](https://medium.com/superhighfives/an-almost-static-stack-6df0a2791319). ## Injecting Data from the Server into the Page Similarly to the previous section, you can leave some placeholders in the HTML that inject global variables, for example: ```js ``` Then, on the server, you can replace `__SERVER_DATA__` with a JSON of real data right before sending the response. The client code can then read `window.SERVER_DATA` to use it. **Make sure to [sanitize the JSON before sending it to the client](https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0) as it makes your app vulnerable to XSS attacks.** ## Running Tests >Note: this feature is available with `react-scripts@0.3.0` and higher.
>[Read the migration guide to learn how to enable it in older projects!](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md#migrating-from-023-to-030) Create React App uses [Jest](https://facebook.github.io/jest/) as its test runner. To prepare for this integration, we did a [major revamp](https://facebook.github.io/jest/blog/2016/09/01/jest-15.html) of Jest so if you heard bad things about it years ago, give it another try. Jest is a Node-based runner. This means that the tests always run in a Node environment and not in a real browser. This lets us enable fast iteration speed and prevent flakiness. While Jest provides browser globals such as `window` thanks to [jsdom](https://github.com/tmpvar/jsdom), they are only approximations of the real browser behavior. Jest is intended to be used for unit tests of your logic and your components rather than the DOM quirks. We recommend that you use a separate tool for browser end-to-end tests if you need them. They are beyond the scope of Create React App. ### Filename Conventions Jest will look for test files with any of the following popular naming conventions: * Files with `.js` suffix in `__tests__` folders. * Files with `.test.js` suffix. * Files with `.spec.js` suffix. The `.test.js` / `.spec.js` files (or the `__tests__` folders) can be located at any depth under the `src` top level folder. We recommend to put the test files (or `__tests__` folders) next to the code they are testing so that relative imports appear shorter. For example, if `App.test.js` and `App.js` are in the same folder, the test just needs to `import App from './App'` instead of a long relative path. Colocation also helps find tests more quickly in larger projects. ### Command Line Interface When you run `npm test`, Jest will launch in the watch mode. Every time you save a file, it will re-run the tests, just like `npm start` recompiles the code. The watcher includes an interactive command-line interface with the ability to run all tests, or focus on a search pattern. It is designed this way so that you can keep it open and enjoy fast re-runs. You can learn the commands from the “Watch Usage” note that the watcher prints after every run: ![Jest watch mode](http://facebook.github.io/jest/img/blog/15-watch.gif) ### Version Control Integration By default, when you run `npm test`, Jest will only run the tests related to files changed since the last commit. This is an optimization designed to make your tests run fast regardless of how many tests you have. However it assumes that you don’t often commit the code that doesn’t pass the tests. Jest will always explicitly mention that it only ran tests related to the files changed since the last commit. You can also press `a` in the watch mode to force Jest to run all tests. Jest will always run all tests on a [continuous integration](#continuous-integration) server or if the project is not inside a Git or Mercurial repository. ### Writing Tests To create tests, add `it()` (or `test()`) blocks with the name of the test and its code. You may optionally wrap them in `describe()` blocks for logical grouping but this is neither required nor recommended. Jest provides a built-in `expect()` global function for making assertions. A basic test could look like this: ```js import sum from './sum'; it('sums numbers', () => { expect(sum(1, 2)).toEqual(3); expect(sum(2, 2)).toEqual(4); }); ``` All `expect()` matchers supported by Jest are [extensively documented here](https://facebook.github.io/jest/docs/en/expect.html#content).
You can also use [`jest.fn()` and `expect(fn).toBeCalled()`](https://facebook.github.io/jest/docs/en/expect.html#tohavebeencalled) to create “spies” or mock functions. ### Testing Components There is a broad spectrum of component testing techniques. They range from a “smoke test” verifying that a component renders without throwing, to shallow rendering and testing some of the output, to full rendering and testing component lifecycle and state changes. Different projects choose different testing tradeoffs based on how often components change, and how much logic they contain. If you haven’t decided on a testing strategy yet, we recommend that you start with creating simple smoke tests for your components: ```js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; it('renders without crashing', () => { const div = document.createElement('div'); ReactDOM.render(, div); }); ``` This test mounts a component and makes sure that it didn’t throw during rendering. Tests like this provide a lot of value with very little effort so they are great as a starting point, and this is the test you will find in `src/App.test.js`. When you encounter bugs caused by changing components, you will gain a deeper insight into which parts of them are worth testing in your application. This might be a good time to introduce more specific tests asserting specific expected output or behavior. If you’d like to test components in isolation from the child components they render, we recommend using [`shallow()` rendering API](http://airbnb.io/enzyme/docs/api/shallow.html) from [Enzyme](http://airbnb.io/enzyme/). To install it, run: ```sh npm install --save enzyme enzyme-adapter-react-16 react-test-renderer ``` Alternatively you may use `yarn`: ```sh yarn add enzyme enzyme-adapter-react-16 react-test-renderer ``` As of Enzyme 3, you will need to install Enzyme along with an Adapter corresponding to the version of React you are using. (The examples above use the adapter for React 16.) The adapter will also need to be configured in your [global setup file](#initializing-test-environment): #### `src/setupTests.js` ```js import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; configure({ adapter: new Adapter() }); ``` >Note: Keep in mind that if you decide to "eject" before creating `src/setupTests.js`, the resulting `package.json` file won't contain any reference to it. [Read here](#initializing-test-environment) to learn how to add this after ejecting. Now you can write a smoke test with it: ```js import React from 'react'; import { shallow } from 'enzyme'; import App from './App'; it('renders without crashing', () => { shallow(); }); ``` Unlike the previous smoke test using `ReactDOM.render()`, this test only renders `` and doesn’t go deeper. For example, even if `` itself renders a `