Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Extends Developer Experience

Yosuke Kurami
February 23, 2020

Extends Developer Experience

TypeScriptとDXのお話

Yosuke Kurami

February 23, 2020
Tweet

More Decks by Yosuke Kurami

Other Decks in Programming

Transcript

  1. About me - Yosuke KuramiʢGitHub, Twitter: @Quramyʣ - Web frontend

    developer - 5೥͘Β͍TypeScript΍ͬͯ·͢
  2. What’s DX ? - ͦ΋ͦ΋Developer ExperienceͬͯͳΜͩΖ͏ - ΋ͱ΋ͱ͸User ExperienceʹΠϯεύΠΞ͞Εͨݴ༿ -

    ʮ։ൃऀ͕ετϨεແ͘γεςϜΛ։ൃɾอकͰ͖Δ͜ͱʯ - ࣗಈԽɺ෼͔Γ΍͍͢APIυΩϡϝϯτɺetc...
  3. Bad DX example - ͜ͷίʔυͷةݥੑ: - data is undefined -

    response͸dataͷkeyͱͯ͠ଘࡏ͍ͯ͠Δͷ͔ʁ function fn(someData) { return data.response; }
  4. With TypeScript - TypeScriptͰΧόʔͰ͖Δ͜ͱ: - ੩తղੳʹΑΔΤϥʔͷࣄલݕ஌ - ར༻ՄೳͳϓϩύςΟͷࣗಈิ׬ - etc…

    - ʮόάΛগͳͤ͘͞Δʯͱ͍͏ΞϑΥʔμϯεΛܗ੒͍ͯ͠Δ function fn(someData: DataType) { return someData.response; }
  5. The unknown - TypeScript͕༧ଌͣ͠Β͍ྖҬ - JavaScriptҎ֎ͷΤίγεςϜͱͷ࿈ܞ - e.g. API req/res,

    database access, CSS / HTML template, etc... - ೗ԿʹunknownΛݮΒ͔͕͢DX޲্ͷ伴
  6. Case 1-1. CSS - CSS Modules - scoped cssͷํ๏࿦ͷ1ͭɻclass໊ΛES2015 moduleͷΑ͏ʹѻ͏

    /* Card.css */ .card { border-radius: 4px; padding: 16px; background-color: white; } .title { font-weight: bold; } /* Card.tsx */ import React from "react"; import styles from "./Card.css"; export default () => ( <div className={styles.card}> <span className={styles.title}>Title</span> </div> );
  7. Case 1-1. CSS - CSSʹରԠ͢Δ.d.tsΛ༻ҙ͢Ε͹ɺTypeScript͔ΒimportͰ͖Δ - https://github.com/Quramy/typed-css-modules /* Card.tsx */

    import React from "react"; import styles from "./Card.css"; export default () => ( <div className={styles.card}> <span className={styles.title}>Title</span> </div> ); /* Card.css.d.ts */ declare const styles: { readonly "card": string; readonly "title": string; }; export = styles
  8. Case 1-2. GraphQL data type - GraphQLͷಛ௃ - ੩తʹܕ෇͚͞ΕͨSchema͕ଘࡏ͢Δ -

    ʢSchemaͰڐ༰͞ΕΔൣғͰʣࣗ༝ʹσʔλΛ໰͍߹ΘͤՄೳ
  9. Case 1-2. GraphQL data type import gql from "graphql-tag"; import

    { QueryResult } from "./__generated__/anonymous-query"; const myQuery = gql` query { viewer { repositories(last: 3) { nodes { name languages(first: 10) { nodes { name } } } } } } `; const { data } = await client.query<QueryResult>({ query: myQuery }); console.log(data?.viewer?.repositories);
  10. Case 1-2. GraphQL data type - QueryͱSchema৘ใΛݩʹɺ݁ՌͱͳΔ QueryResult typeΛࣗಈੜ੒ -

    https://github.com/Quramy/ts-graphql-plugin - /* eslint-disable */ /* This is an autogenerated file. Do not edit this file directly! */ export type QueryResult = { viewer: { repositories: { nodes: (({ name: string; languages: ({ nodes: (({ name: string; }) | null)[] | null; }) | null; }) | null)[] | null; }; }; };
  11. Code generation flow ίʔυੜ੒ͷྲྀΕɿ - ผݴޠΛղੳͯ͠ߏ଄Խ͞Εͨ৘ใΛಘΔ - e.g. GraphQL Schema,

    CSS Selector, Flow Type (Babylon) - ߏ଄Խ৘ใΛݩʹTypeScriptͷASTʢந৅ߏจ໦ʣΛ࡞Δ - ASTΛςΩετදݱʹͯ͠ɺϑΝΠϧͱͯ͠อଘ͢Δ
  12. Abstract syntax tree export type Data = { hoge: string;

    }; ts.createTypeAliasDeclaration( undefined, [ts.createModifier(ts.SyntaxKind.ExportKeyword)], ts.createIdentifier("Data"), undefined, ts.createTypeLiteralNode([ts.createPropertySignature( undefined, ts.createIdentifier("hoge"), undefined, ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), undefined )]), );
  13. Generate code from AST - ASTΛςΩετදݱʹunparse͢Δʹ͸ɺts.PrinterΛར༻͢Δ const node = ts.createTypeLiteralNode(...);

    const sourceText = ts.createPrinter().printNode( ts.EmitHint.Unspecified, node, ts.createSourceFile("generated.ts", "", ts.ScriptTarget.Latest), ); ts.sys.writeFile("generated.ts", sourceText);
  14. Where can I check ? - ࠓ೔ͷΩʔϫʔυ͸ʮ༧ଌՄೳੑʯ - Ͳͷ࣌఺ͰΤϥʔΛ༧ଌ͢Δʁ CI

    or Git client hook or ... - ૣظʹ༧ଌͰ͖Ε͹Ͱ͖Δ΄ͲʮDX͕ߴ͍ʯ͸ͣ - Α͠ΤσΟλͰ΍ͬͯ͠·͓͏
  15. Case 2-2. GraphQL query - https://github.com/Quramy/ts-graphql-plugin - GraphQLͷΫΤϦهड़Λิॿ͢ΔLanguage service plugin

    /* tsconfig.json */
 
 { "compilerOptions": { "plugins": [ { "name": "ts-graphql-plugin", // plugin NPM package name "schema": "schema.graphql", "tag": "gql" } ] } }
  16. Other plugins - Template string literalΛѻ͏ϥΠϒϥϦΛର৅ʹ࡞੒͞ΕΔ͜ͱ͕ଟ͍ - Angular: HTML templateதͷΤϥʔνΣοΫ΍ิ׬

    https://www.npmjs.com/package/@angular/language-service - lit-html: HTML templateதͷิ׬ https://www.npmjs.com/package/ts-lit-plugin - styled-components: CSS attributesͷิ׬ https://www.npmjs.com/package/typescript-styled-plugin
  17. Advantages of language service - TypeScript server (a.k.a. tsserver) ͷԸܙ

    - RopeʹΑΔString࣮૷΍ɺ૿෼parseͳͲߴ଎ͳج൫ https://quramy.github.io/ts-server-side-anatomy/ - ΤσΟλଆͰ֦ுΛ࡞ΔΑΓ΋ߴ଎ͳΠϯλϥΫγϣϯΛ࣮ݱՄೳ - ଎͚Ε͹଎͍΄Ͳྑ͍
  18. How to create a plugin ? - ts.LanguageServiceΛϥοϓͯ͠ฦ٫͢Δؔ਺Λ࡞Δ͚ͩ import ts

    from "typescript/lib/tsserverlibrary"; function create(info: ts.server.PluginCreateInfo) { // Create a proxy for language service return wrapLangService(info.languageService); } export = () => ({ create });
  19. How to create a plugin ? Language serviceͷ֦ுϙΠϯτʢҰ෦ൈਮʣɿ getCompleteAtPosition ࣗಈิ׬ީิͷฦ٫

    getSemanticDiagnostics Τϥʔ৘ใͷฦ٫ getDefinitionAtPosition ఆٛՕॴ΁δϟϯϓ getQuickInfoAtPosition πʔϧνοϓ
  20. Minimal plugin example function create(info: ts.server.PluginCreateInfo) { const getQuickInfoAtPosition =

    new Proxy( info.languageService.getQuickInfoAtPosition, { apply(delegate, self, args) { const result = delegate.apply(self, args); if (!result) return; return { ...result, displayParts: [ { text: "" }, ...result.displayParts || [], { text: "" }, ], } as typeof result; }, }, ); return { ...info.languageService, getQuickInfoAtPosition, }; }
  21. Plugin method code flow - ϑοΫͨ͠ϝιουͷதͰɺTypeScript ASTΛղੳ͢Δ - ྫ: -

    GraphQLͷέʔεɿTemplate string literalΛநग़͠ɺgraphql-jsͰղ ੳޙɺ݁ՌΛฦ٫͢Δ - ESLintͷέʔεɿTypeScript ASTΛestreeʹม׵͠ɺlinterΛݺͼग़͢
  22. AST traverser example - Template string literalΛ୳ࡧ͢Δίʔυͷྫ function getSemanticDiagnostics(fileName: string)

    { const src = langService.getProgram().getSourceFile(fileName)!; const processTemplate = (node: ts.Node) => { if (ts.isNoSubstitutionTemplateLiteral(node)) { const body = node.getText(); // process text body } else { ts.forEachChild(node, processTemplate); } } ts.forEachChild(src, processTemplate); }
  23. Let’s create your plugin - TypeScript language service plugin͸ͦͦ͜͜ϚχΞοΫͳ࢓૊Έ͚ͩ Ͳɺ΋ͬͱ޿·ͬͯཉ͍͠

    - TypeScriptؔ࿈ͷVSC֦ு࡞੒Λߟ͍͑ͯΔͷͰ͋Ε͹ɺlanguage service plugin΋ݕ౼ͯ͠Έͯ
  24. I talked... Code generation: Writing AST Editor extension: Reading AST

    TypeScriptίʔυͷॻ͖׵͑ Custom transformation
  25. Example 10 ** 3 Math.pow(10, 3) ts.createCall( ts.createPropertyAccess( ts.createIdentifier("Math"), ts.createIdentifier("pow")

    ), undefined, [ ts.createNumericLiteral("10"), ts.createNumericLiteral("3") ], ); ts.createBinary( ts.createNumericLiteral("10"), ts.createToken( ts.SyntaxKind.AsteriskAsteriskToken ), ts.createNumericLiteral("3"), ); Before After parse print Transform
  26. Case 3-1. AoT - ඇJavaScriptͷDSLΛTemplate Stringͱͯ͠.jsʹ࣋ͪࠐΉϑϨʔϜϫʔΫ ͸৭ʑͱ͋Δ - Apollo Client(GraphQL),

    Angular HTML/CSS, Styled Components, lit- html, etc… const query = gql` query { viewer { repositories(last: 3) { nodes { name } } } } `; import { html } from 'lit-html'; const template = (name: string) => html` <div>Hello, ${name}</div> `;
  27. Case 3-1. AoT - ͦ͜ͰAhead of Time compile - Ϗϧυ࣌ʹTemplate

    literalͷத਎Λparseͯ͠தؒදݱʹม׵ - AngularͷngcίϚϯυͳͲ͕༗໊ - https://github.com/Quramy/ts-graphql-plugin Ͱ΋GraphQL ΫΤϦΛ AoTͰม׵͢ΔͨΊͷCustom transformerΛఏڙ
  28. Transformer example - Template string literalΛԿ͔ͷObject literalʹม׵͢Δྫ: - ར༻͢ΔAPI͸গ͠ҟͳΔ͕ɺAST୳ࡧؔ਺Λ࠶ؼݺͼग़͢͠Δͷ͸ Language

    service pluginͷέʔεͱҰॹ function transform(ctx: ts.TransformationContext) { const processTemplate = (node: ts.Node): ts.Node => { if (ts.isNoSubstitutionTemplateLiteral(node)) { return ts.createObjectLiteral(/* ... */); } else { return ts.visitEachChild(node, processTemplate, ctx); } }; return (src: ts.SourceFile) => ts.visitEachChild(src, processTemplate, ctx); }
  29. Summary - TypeScriptͰ֫ಘ͢ΔDX: ʮ༧ଌՄೳੑʯ - TypeScript APIͰ༧ଌՄೳੑΛఈ্͛͢Δ - .tsίʔυΛੜ੒ͯ͠unknownʹܕΛ༩͑Δ -

    .tsίʔυΛղੳͯ͠ΤσΟλʹ৘ใΛಧ͚Δ - ղੳ/ੜ੒ͷ࢓૊ΈΛ࠶ར༻͢Ε͹ɺύϑΥʔϚϯε޲্ʹܨ͕Δ͜ͱ΋
  30. Getting started - TypeScriptͷAPI͸͍ͬͺ͍͋Δ͚Ͳʢࠓ೔঺հͨ͠ͷ΋͘͝Ұ෦ʣ - ·ͣ͸ASTʹ׳ΕΔͷ͕Φεεϝ - ࣍ʹTypeCheckerͳͲͷCompiler APIͱLangage Service

    API - ASTͷಡΈॻ͖ʹ׳ΕͨΒTransformer API͸؆୯ - ASTղੳͷ஌ࣝ͸TypeScriptҎ֎Ͱ΋໾ʹཱͭʢe.g. ESLint rulesʣ