Extends Developer Experience

Extends Developer Experience

TypeScriptとDXのお話

893f54413c2bd9ba41d11d753aacaf2c?s=128

Yosuke Kurami

February 23, 2020
Tweet

Transcript

  1. 2.

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

    developer - 5೥͘Β͍TypeScript΍ͬͯ·͢
  2. 3.
  3. 7.

    What’s DX ? - ͦ΋ͦ΋Developer ExperienceͬͯͳΜͩΖ͏ - ΋ͱ΋ͱ͸User ExperienceʹΠϯεύΠΞ͞Εͨݴ༿ -

    ʮ։ൃऀ͕ετϨεແ͘γεςϜΛ։ൃɾอकͰ͖Δ͜ͱʯ - ࣗಈԽɺ෼͔Γ΍͍͢APIυΩϡϝϯτɺetc...
  4. 9.

    Bad DX example - ͜ͷίʔυͷةݥੑ: - data is undefined -

    response͸dataͷkeyͱͯ͠ଘࡏ͍ͯ͠Δͷ͔ʁ function fn(someData) { return data.response; }
  5. 11.

    With TypeScript - TypeScriptͰΧόʔͰ͖Δ͜ͱ: - ੩తղੳʹΑΔΤϥʔͷࣄલݕ஌ - ར༻ՄೳͳϓϩύςΟͷࣗಈิ׬ - etc…

    - ʮόάΛগͳͤ͘͞Δʯͱ͍͏ΞϑΥʔμϯεΛܗ੒͍ͯ͠Δ function fn(someData: DataType) { return someData.response; }
  6. 13.

    The unknown - TypeScript͕༧ଌͣ͠Β͍ྖҬ - JavaScriptҎ֎ͷΤίγεςϜͱͷ࿈ܞ - e.g. API req/res,

    database access, CSS / HTML template, etc... - ೗ԿʹunknownΛݮΒ͔͕͢DX޲্ͷ伴
  7. 16.

    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> );
  8. 17.

    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
  9. 18.

    Case 1-2. GraphQL data type - GraphQLͷಛ௃ - ੩తʹܕ෇͚͞ΕͨSchema͕ଘࡏ͢Δ -

    ʢSchemaͰڐ༰͞ΕΔൣғͰʣࣗ༝ʹσʔλΛ໰͍߹ΘͤՄೳ
  10. 19.

    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);
  11. 20.

    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; }; }; };
  12. 21.

    Code generation flow ίʔυੜ੒ͷྲྀΕɿ - ผݴޠΛղੳͯ͠ߏ଄Խ͞Εͨ৘ใΛಘΔ - e.g. GraphQL Schema,

    CSS Selector, Flow Type (Babylon) - ߏ଄Խ৘ใΛݩʹTypeScriptͷASTʢந৅ߏจ໦ʣΛ࡞Δ - ASTΛςΩετදݱʹͯ͠ɺϑΝΠϧͱͯ͠อଘ͢Δ
  13. 22.

    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 )]), );
  14. 23.

    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);
  15. 27.

    Where can I check ? - ࠓ೔ͷΩʔϫʔυ͸ʮ༧ଌՄೳੑʯ - Ͳͷ࣌఺ͰΤϥʔΛ༧ଌ͢Δʁ CI

    or Git client hook or ... - ૣظʹ༧ଌͰ͖Ε͹Ͱ͖Δ΄ͲʮDX͕ߴ͍ʯ͸ͣ - Α͠ΤσΟλͰ΍ͬͯ͠·͓͏
  16. 29.

    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" } ] } }
  17. 30.
  18. 32.

    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
  19. 34.

    Advantages of language service - TypeScript server (a.k.a. tsserver) ͷԸܙ

    - RopeʹΑΔString࣮૷΍ɺ૿෼parseͳͲߴ଎ͳج൫ https://quramy.github.io/ts-server-side-anatomy/ - ΤσΟλଆͰ֦ுΛ࡞ΔΑΓ΋ߴ଎ͳΠϯλϥΫγϣϯΛ࣮ݱՄೳ - ଎͚Ε͹଎͍΄Ͳྑ͍
  20. 35.

    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 });
  21. 36.

    How to create a plugin ? Language serviceͷ֦ுϙΠϯτʢҰ෦ൈਮʣɿ getCompleteAtPosition ࣗಈิ׬ީิͷฦ٫

    getSemanticDiagnostics Τϥʔ৘ใͷฦ٫ getDefinitionAtPosition ఆٛՕॴ΁δϟϯϓ getQuickInfoAtPosition πʔϧνοϓ
  22. 37.

    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, }; }
  23. 38.

    Plugin method code flow - ϑοΫͨ͠ϝιουͷதͰɺTypeScript ASTΛղੳ͢Δ - ྫ: -

    GraphQLͷέʔεɿTemplate string literalΛநग़͠ɺgraphql-jsͰղ ੳޙɺ݁ՌΛฦ٫͢Δ - ESLintͷέʔεɿTypeScript ASTΛestreeʹม׵͠ɺlinterΛݺͼग़͢
  24. 39.

    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); }
  25. 40.

    Let’s create your plugin - TypeScript language service plugin͸ͦͦ͜͜ϚχΞοΫͳ࢓૊Έ͚ͩ Ͳɺ΋ͬͱ޿·ͬͯཉ͍͠

    - TypeScriptؔ࿈ͷVSC֦ு࡞੒Λߟ͍͑ͯΔͷͰ͋Ε͹ɺlanguage service plugin΋ݕ౼ͯ͠Έͯ
  26. 42.

    I talked... Code generation: Writing AST Editor extension: Reading AST

    TypeScriptίʔυͷॻ͖׵͑ Custom transformation
  27. 44.

    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
  28. 47.

    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> `;
  29. 49.

    Case 3-1. AoT - ͦ͜ͰAhead of Time compile - Ϗϧυ࣌ʹTemplate

    literalͷத਎Λparseͯ͠தؒදݱʹม׵ - AngularͷngcίϚϯυͳͲ͕༗໊ - https://github.com/Quramy/ts-graphql-plugin Ͱ΋GraphQL ΫΤϦΛ AoTͰม׵͢ΔͨΊͷCustom transformerΛఏڙ
  30. 50.

    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); }
  31. 52.
  32. 53.

    Summary - TypeScriptͰ֫ಘ͢ΔDX: ʮ༧ଌՄೳੑʯ - TypeScript APIͰ༧ଌՄೳੑΛఈ্͛͢Δ - .tsίʔυΛੜ੒ͯ͠unknownʹܕΛ༩͑Δ -

    .tsίʔυΛղੳͯ͠ΤσΟλʹ৘ใΛಧ͚Δ - ղੳ/ੜ੒ͷ࢓૊ΈΛ࠶ར༻͢Ε͹ɺύϑΥʔϚϯε޲্ʹܨ͕Δ͜ͱ΋
  33. 54.

    Getting started - TypeScriptͷAPI͸͍ͬͺ͍͋Δ͚Ͳʢࠓ೔঺հͨ͠ͷ΋͘͝Ұ෦ʣ - ·ͣ͸ASTʹ׳ΕΔͷ͕Φεεϝ - ࣍ʹTypeCheckerͳͲͷCompiler APIͱLangage Service

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