diff --git a/examples/with-firebase-hosting-and-typescript/.firebaserc b/examples/with-firebase-hosting-and-typescript/.firebaserc new file mode 100644 index 00000000..c1330d90 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "" + } +} diff --git a/examples/with-firebase-hosting-and-typescript/.gitignore b/examples/with-firebase-hosting-and-typescript/.gitignore new file mode 100644 index 00000000..849ddff3 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/examples/with-firebase-hosting-and-typescript/README.md b/examples/with-firebase-hosting-and-typescript/README.md new file mode 100644 index 00000000..9d07fb78 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/README.md @@ -0,0 +1,74 @@ +# With Firebase Hosting and Typescript example + +## How to use + +### Using `create-next-app` + +Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: + +```bash +npx create-next-app --example with-firebase-hosting-and-typescript with-firebase-hosting-and-typescript-app +# or +yarn create next-app --example with-firebase-hosting-and-typescript with-firebase-hosting-and-typescript-app +``` + +### Download manually + +Download the example [or clone the repo](https://github.com/zeit/next.js): + +```bash +curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-firebase-hosting-and-typescript +cd with-firebase-hosting-and-typescript +``` + +Set up firebase: + +* install Firebase Tools: `npm i -g firebase-tools` +* create a project through the [firebase web console](https://console.firebase.google.com/) +* grab the projects ID from the web consoles URL: https://console.firebase.google.com/project/ +* update the `.firebaserc` default project ID to the newly created project +* login to the Firebase CLI tool with `firebase login` + +#### Install project: + +```bash +npm install +``` + +#### Run Next.js development: + +```bash +npm run dev +``` + +#### Run Firebase locally for testing: + +``` +npm run serve +``` + +#### Deploy it to the cloud with Firebase: + +```bash +npm run deploy +``` + +#### Clean dist folder + +```bash +npm run clean +``` + +## The idea behind the example + +The goal is to host the Next.js app on Firebase Cloud Functions with Firebase Hosting rewrite rules so our app is served from our Firebase Hosting URL, with a complete Typescript stack for both the Next app and for the Firebase Functions. Each individual `page` bundle is served in a new call to the Cloud Function which performs the initial server render. + +This is based off of the work of @jthegedus in the [with-firebase-hosting](https://github.com/zeit/next.js/tree/canary/examples/with-firebase-hosting) example. + +If you're having issues, feel free to tag @sampsonjoliver in the [issue you create on the next.js repo](https://github.com/zeit/next.js/issues/new) + +## Important + +* The empty `placeholder.html` file is so Firebase Hosting does not error on an empty `public/` folder and still hosts at the Firebase project URL. +* `firebase.json` outlines the catchall rewrite rule for our Cloud Function. +* The [Firebase predeploy](https://firebase.google.com/docs/cli/#predeploy_and_postdeploy_hooks) hooks defined in `firebase.json` will handle linting and compiling of the next app and the functions sourceswhen `firebase deploy` is invoked. The only scripts you should need are `dev`, `clean` and `deploy`. diff --git a/examples/with-firebase-hosting-and-typescript/firebase.json b/examples/with-firebase-hosting-and-typescript/firebase.json new file mode 100644 index 00000000..bb8ace29 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/firebase.json @@ -0,0 +1,24 @@ +{ + "functions": { + "source": "dist/functions", + "predeploy": [ + "npm run lint-functions", + "npm run lint-app", + "npm run typecheck-app", + "npm run build-functions", + "npm run build-app", + "npm run copy-deps", + "npm run install-deps" + ] + }, + "hosting": { + "public": "dist/public", + "rewrites": [ + { + "source": "**/**", + "function": "nextApp" + } + ], + "predeploy": "npm run build-public" + } +} diff --git a/examples/with-firebase-hosting-and-typescript/package.json b/examples/with-firebase-hosting-and-typescript/package.json new file mode 100644 index 00000000..48d5df2d --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/package.json @@ -0,0 +1,38 @@ +{ + "name": "with-firebase-hosting", + "version": "1.0.0", + "description": "Host Next.js SSR app on Firebase Cloud Functions with Firebase Hosting redirects.", + "scripts": { + "dev": "next src/app", + "serve": "NODE_ENV=production firebase serve --only functions,hosting", + "deploy": "firebase deploy", + "clean": "rimraf \"dist/functions\" && rimraf \"dist/public\"", + "build-app": "next build \"src/app\"", + "build-public": "cpx \"src/public/**/*.*\" \"dist/public\" -C", + "build-functions": "tsc --project src/functions", + "lint-app": "tslint --project src/app", + "typecheck-app": "tsc --project src/app", + "lint-functions": "tslint --project src/functions", + "copy-deps": "cpx \"*{package.json,package-lock.json,yarn.lock}\" \"dist/functions\"", + "install-deps": "cd \"dist/functions\" && npm i" + }, + "dependencies": { + "@zeit/next-typescript": "^1.0.0", + "firebase-admin": "~5.12.1", + "firebase-functions": "^1.0.1", + "next": "^6.0.3", + "react": "^16.3.2", + "react-dom": "^16.3.2" + }, + "devDependencies": { + "@types/next": "^2.4.10", + "@types/react": "^16.3.14", + "cpx": "1.5.0", + "firebase-tools": "3.18.4", + "prettier": "1.12.1", + "rimraf": "2.6.2", + "tslint": "^5.8.0", + "tslint-react": "3.6.0", + "typescript": "^2.5.3" + } +} diff --git a/examples/with-firebase-hosting-and-typescript/src/app/.babelrc b/examples/with-firebase-hosting-and-typescript/src/app/.babelrc new file mode 100644 index 00000000..aa1a4ef8 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/app/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["next/babel", "@zeit/next-typescript/babel"] +} diff --git a/examples/with-firebase-hosting-and-typescript/src/app/components/App.tsx b/examples/with-firebase-hosting-and-typescript/src/app/components/App.tsx new file mode 100644 index 00000000..f4818de8 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/app/components/App.tsx @@ -0,0 +1,10 @@ +import Header from './Header'; + +const App = ({ children }: { children?: any }) => ( +
+
+ {children} +
+); + +export default App; diff --git a/examples/with-firebase-hosting-and-typescript/src/app/components/Header.tsx b/examples/with-firebase-hosting-and-typescript/src/app/components/Header.tsx new file mode 100644 index 00000000..bd9595ed --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/app/components/Header.tsx @@ -0,0 +1,12 @@ +import Link from 'next/link'; + +export default ({ pathname }: { pathname?: any }) => ( +
+ + Home + + + About + +
+); diff --git a/examples/with-firebase-hosting-and-typescript/src/app/next.config.js b/examples/with-firebase-hosting-and-typescript/src/app/next.config.js new file mode 100644 index 00000000..3e3c2e3d --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/app/next.config.js @@ -0,0 +1,2 @@ +const withTypescript = require('@zeit/next-typescript') +module.exports = withTypescript({ distDir: '../../dist/functions/next' }) diff --git a/examples/with-firebase-hosting-and-typescript/src/app/pages/about.tsx b/examples/with-firebase-hosting-and-typescript/src/app/pages/about.tsx new file mode 100644 index 00000000..4daf51e9 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/app/pages/about.tsx @@ -0,0 +1,7 @@ +import App from '../components/App'; + +export default () => ( + +

About Page

+
+); diff --git a/examples/with-firebase-hosting-and-typescript/src/app/pages/index.tsx b/examples/with-firebase-hosting-and-typescript/src/app/pages/index.tsx new file mode 100644 index 00000000..edf30078 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/app/pages/index.tsx @@ -0,0 +1,7 @@ +import App from '../components/App'; + +export default () => ( + +

Index Page

+
+); diff --git a/examples/with-firebase-hosting-and-typescript/src/app/tsconfig.json b/examples/with-firebase-hosting-and-typescript/src/app/tsconfig.json new file mode 100644 index 00000000..8c7b1dc3 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/app/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "allowJs": false, + "jsx": "preserve", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "noImplicitAny": false, + "strict": true, + "strictNullChecks": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "preserveConstEnums": true, + "skipLibCheck": true, + "sourceMap": true, + "noEmit": true, + "lib": [ + "es6", + "dom", + "es2016" + ], + "baseUrl": ".", + } +} diff --git a/examples/with-firebase-hosting-and-typescript/src/app/tslint.json b/examples/with-firebase-hosting-and-typescript/src/app/tslint.json new file mode 100644 index 00000000..8bf922af --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/app/tslint.json @@ -0,0 +1,7 @@ +{ + "extends": ["tslint:latest", "tslint-react"], + "rules": { + "quotemark": [true, "single"], + "no-submodule-imports": false + } +} diff --git a/examples/with-firebase-hosting-and-typescript/src/functions/src/app/next.ts b/examples/with-firebase-hosting-and-typescript/src/functions/src/app/next.ts new file mode 100644 index 00000000..061f11a3 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/functions/src/app/next.ts @@ -0,0 +1,11 @@ +import * as functions from 'firebase-functions'; +const next = require('next'); + +const dev = process.env.NODE_ENV !== 'production'; +const app = next({ dev, conf: { distDir: 'next' } }); +const handle = app.getRequestHandler(); + +export const nextApp = functions.https.onRequest((req, res) => { + console.log('File: ' + req.originalUrl); + return app.prepare().then(() => handle(req, res)); +}); diff --git a/examples/with-firebase-hosting-and-typescript/src/functions/src/index.ts b/examples/with-firebase-hosting-and-typescript/src/functions/src/index.ts new file mode 100644 index 00000000..6b630050 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/functions/src/index.ts @@ -0,0 +1,3 @@ +import * as functions from 'firebase-functions'; + +export { nextApp } from './app/next'; \ No newline at end of file diff --git a/examples/with-firebase-hosting-and-typescript/src/functions/tsconfig.json b/examples/with-firebase-hosting-and-typescript/src/functions/tsconfig.json new file mode 100644 index 00000000..04348322 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/functions/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "lib": ["es6"], + "module": "commonjs", + "noImplicitReturns": true, + "outDir": "../../dist/functions", + "sourceMap": true, + "target": "es6", + "baseUrl": "./src" + }, + "compileOnSave": true, + "include": ["src"] +} diff --git a/examples/with-firebase-hosting-and-typescript/src/functions/tslint.json b/examples/with-firebase-hosting-and-typescript/src/functions/tslint.json new file mode 100644 index 00000000..4d35c4e1 --- /dev/null +++ b/examples/with-firebase-hosting-and-typescript/src/functions/tslint.json @@ -0,0 +1,121 @@ +{ + "rules": { + // -- Strict errors -- + // These lint rules are likely always a good idea. + + // Force function overloads to be declared together. This ensures readers understand APIs. + "adjacent-overload-signatures": true, + + // Do not allow the subtle/obscure comma operator. + "ban-comma-operator": true, + + // Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules. + "no-namespace": true, + + // Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars. + "no-parameter-reassignment": true, + + // Force the use of ES6-style imports instead of /// imports. + "no-reference": true, + + // Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the + // code currently being edited (they may be incorrectly handling a different type case that does not exist). + "no-unnecessary-type-assertion": true, + + // Disallow nonsensical label usage. + "label-position": true, + + // Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }. + "no-conditional-assignment": true, + + // Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed). + "no-construct": true, + + // Do not allow super() to be called twice in a constructor. + "no-duplicate-super": true, + + // Do not allow the same case to appear more than once in a switch block. + "no-duplicate-switch-case": true, + + // Do not allow a variable to be declared more than once in the same block. Consider function parameters in this + // rule. + "no-duplicate-variable": [true, "check-parameters"], + + // Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should + // instead use a separate variable name. + "no-shadowed-variable": true, + + // Empty blocks are almost never needed. Allow the one general exception: empty catch blocks. + "no-empty": [true, "allow-empty-catch"], + + // Functions must either be handled directly (e.g. with a catch() handler) or returned to another function. + // This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on. + "no-floating-promises": true, + + // Do not allow any imports for modules that are not in package.json. These will almost certainly fail when + // deployed. + "no-implicit-dependencies": true, + + // The 'this' keyword can only be used inside of classes. + "no-invalid-this": true, + + // Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead. + "no-string-throw": true, + + // Disallow control flow statements, such as return, continue, break, and throw in finally blocks. + "no-unsafe-finally": true, + + // Do not allow variables to be used before they are declared. + "no-use-before-declare": true, + + // Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid(); + "no-void-expression": [true, "ignore-arrow-function-shorthand"], + + // Disallow duplicate imports in the same file. + "no-duplicate-imports": true, + + + // -- Strong Warnings -- + // These rules should almost never be needed, but may be included due to legacy code. + // They are left as a warning to avoid frustration with blocked deploys when the developer + // understand the warning and wants to deploy anyway. + + // Warn when an empty interface is defined. These are generally not useful. + "no-empty-interface": {"severity": "warning"}, + + // Warn when an import will have side effects. + "no-import-side-effect": {"severity": "warning"}, + + // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for + // most values and let for values that will change. + "no-var-keyword": {"severity": "warning"}, + + // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental. + "triple-equals": {"severity": "warning"}, + + // Warn when using deprecated APIs. + "deprecation": {"severity": "warning"}, + + // -- Light Warnigns -- + // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info" + // if TSLint supported such a level. + + // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array. + // (Even better: check out utils like .map if transforming an array!) + "prefer-for-of": {"severity": "warning"}, + + // Warns if function overloads could be unified into a single function with optional or rest parameters. + "unified-signatures": {"severity": "warning"}, + + // Warns if code has an import or variable that is unused. + "no-unused-variable": {"severity": "warning"}, + + // Prefer const for values that will not change. This better documents code. + "prefer-const": {"severity": "warning"}, + + // Multi-line object liiterals and function calls should have a trailing comma. This helps avoid merge conflicts. + "trailing-comma": {"severity": "warning"} + }, + + "defaultSeverity": "error" +} diff --git a/examples/with-firebase-hosting-and-typescript/src/public/placeholder.html b/examples/with-firebase-hosting-and-typescript/src/public/placeholder.html new file mode 100644 index 00000000..e69de29b diff --git a/test/integration/basic/components/hello.jsx b/test/integration/basic/components/hello.jsx index 3a9fb038..c804c862 100644 --- a/test/integration/basic/components/hello.jsx +++ b/test/integration/basic/components/hello.jsx @@ -1,3 +1,3 @@ export const Hello = () => (
Hello
- ) +) diff --git a/test/integration/basic/components/world.jsx b/test/integration/basic/components/world.jsx index 89737546..3a56d054 100644 --- a/test/integration/basic/components/world.jsx +++ b/test/integration/basic/components/world.jsx @@ -1,3 +1,3 @@ export const World = () => (
World
- ) +)