$30 off During Our Annual Pro Sale. View Details »

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. Extends Developer Experience Yosuke Kurami (@Quramy)

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

    developer - 5೥͘Β͍TypeScript΍ͬͯ·͢
  3. None
  4. TypeScript APIΛ࢖ͬͨ πʔϧ࡞Γ͸ָ͍ͧ͠

  5. Today’s goal - TypeScriptΛ࢖ͬͨπʔϧͰշద͞ΛखʹೖΕΑ͏ʂ - ͦΜͳ͜ͱͰ͖Δͷʁͱ͍͏ؾ෇͖ʹͳͬͯ͘ΕΔͱخ͍͠ - TypeScript APIͰԿ͔࡞Ζ͏ʂͱ͍͏ਓ͕૿͑ͯཉ͍͠

  6. TypeScript and DX

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

    ʮ։ൃऀ͕ετϨεແ͘γεςϜΛ։ൃɾอकͰ͖Δ͜ͱʯ - ࣗಈԽɺ෼͔Γ΍͍͢APIυΩϡϝϯτɺetc...
  8. Bad DX example - ࢼ͠ʹʮDX͕ѱ͍ঢ়ଶʯΛߟ͑ͯΈΔ - ࣍ͷJavaScriptͷίʔυ͕͋ͬͨͱͯ͠ɿ function fn(someData) {

    return data.response; }
  9. Bad DX example - ͜ͷίʔυͷةݥੑ: - data is undefined -

    response͸dataͷkeyͱͯ͠ଘࡏ͍ͯ͠Δͷ͔ʁ function fn(someData) { return data.response; }
  10. Bad DX example - ΋͠ؾ෇͔ͣʹຊ൪ྲྀग़ͯ͠ো֐ʹͳͬͨͱ͢Δͱɿ - Өڹௐࠪ - hot fixͷ࡞੒/ϨϏϡʔ/σϓϩΠ

    - ৼΓฦΓ - ਺ߦͷमਖ਼Ͱ͋ͬͯ΋ɺ࣌ؒͱMP͕େྔʹ࡟ΒΕΔ
  11. With TypeScript - TypeScriptͰΧόʔͰ͖Δ͜ͱ: - ੩తղੳʹΑΔΤϥʔͷࣄલݕ஌ - ར༻ՄೳͳϓϩύςΟͷࣗಈิ׬ - etc…

    - ʮόάΛগͳͤ͘͞Δʯͱ͍͏ΞϑΥʔμϯεΛܗ੒͍ͯ͠Δ function fn(someData: DataType) { return someData.response; }
  12. Predictability - TypeScript͕JavaScriptΤϯδχΞʹ΋ͨΒͨ͠΋ͷɿ༧ଌՄೳੑ - TypeScripterʹͱͬͯʮDXΛ֦ு͢Δʯͱ͸ɿ - ༧ଌՄೳͳྖҬΛ޿͛Δ͜ͱ

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

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

  15. Automatic type generation - ֎෦͔Β unknown ͕΍ͬͯ͘Δ - TypeScriptͷܕఆٛΛػցతʹ༩͑ͯ͠·͑ʂ

  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> );
  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
  18. Case 1-2. GraphQL data type - GraphQLͷಛ௃ - ੩తʹܕ෇͚͞ΕͨSchema͕ଘࡏ͢Δ -

    ʢSchemaͰڐ༰͞ΕΔൣғͰʣࣗ༝ʹσʔλΛ໰͍߹ΘͤՄೳ
  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);
  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; }; }; };
  21. Code generation flow ίʔυੜ੒ͷྲྀΕɿ - ผݴޠΛղੳͯ͠ߏ଄Խ͞Εͨ৘ใΛಘΔ - e.g. GraphQL Schema,

    CSS Selector, Flow Type (Babylon) - ߏ଄Խ৘ใΛݩʹTypeScriptͷASTʢந৅ߏจ໦ʣΛ࡞Δ - ASTΛςΩετදݱʹͯ͠ɺϑΝΠϧͱͯ͠อଘ͢Δ
  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 )]), );
  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);
  24. Tips - TypeScript ASTͱίʔυͷؔ܎ΛݟΔʹ͸AST viewer͕͓͢͢Ί - https://ts-ast-viewer.com - ASTΛੜ੒͢ΔͨΊͷίʔυεχϖοτ΋දࣔͯ͘͠ΕΔ

  25. TS AST viewer

  26. 2. Editor extension

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

    or Git client hook or ... - ૣظʹ༧ଌͰ͖Ε͹Ͱ͖Δ΄ͲʮDX͕ߴ͍ʯ͸ͣ - Α͠ΤσΟλͰ΍ͬͯ͠·͓͏
  28. Language service plugin - Language serviceɿΤσΟλʹΤϥʔ৘ใ΍ิ׬ީิΛಧ͚Δ໾ׂ ʢVSCͰαΫαΫίʔυ͕ॻ͚Δͷ΋ίΠπͷ͓ӄʣ - Language serviceʹ͸Ϣʔβʔಠࣗͷplugin͕ઃఆՄೳ

  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" } ] } }
  30. None
  31. Case 2-2. ESLint - https://github.com/Quramy/typescript-eslint-language-service -

  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
  33. Advantages of language service - ΤσΟλͷछผΛ໰Θͳ͍ - VSCͩͷvimͩͷemacsͩͷɺफڭ࿦૪ʹؔΘΒͳͯ͘ࡁΉ

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

    - RopeʹΑΔString࣮૷΍ɺ૿෼parseͳͲߴ଎ͳج൫ https://quramy.github.io/ts-server-side-anatomy/ - ΤσΟλଆͰ֦ுΛ࡞ΔΑΓ΋ߴ଎ͳΠϯλϥΫγϣϯΛ࣮ݱՄೳ - ଎͚Ε͹଎͍΄Ͳྑ͍
  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 });
  36. How to create a plugin ? Language serviceͷ֦ுϙΠϯτʢҰ෦ൈਮʣɿ getCompleteAtPosition ࣗಈิ׬ީิͷฦ٫

    getSemanticDiagnostics Τϥʔ৘ใͷฦ٫ getDefinitionAtPosition ఆٛՕॴ΁δϟϯϓ getQuickInfoAtPosition πʔϧνοϓ
  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, }; }
  38. Plugin method code flow - ϑοΫͨ͠ϝιουͷதͰɺTypeScript ASTΛղੳ͢Δ - ྫ: -

    GraphQLͷέʔεɿTemplate string literalΛநग़͠ɺgraphql-jsͰղ ੳޙɺ݁ՌΛฦ٫͢Δ - ESLintͷέʔεɿTypeScript ASTΛestreeʹม׵͠ɺlinterΛݺͼग़͢
  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); }
  40. Let’s create your plugin - TypeScript language service plugin͸ͦͦ͜͜ϚχΞοΫͳ࢓૊Έ͚ͩ Ͳɺ΋ͬͱ޿·ͬͯཉ͍͠

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

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

    TypeScriptίʔυͷॻ͖׵͑ Custom transformation
  43. What’s transformer ? - ASTΛผͷASTʹม׵͢Δ࢓૊Έ - Babel pluginͷTypeScript൛ - TypeScriptຊମ΋transformerͷ૊Έ߹ΘͤͰͰ͖͍ͯΔ

  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
  45. Caveat - Custom transformer͸tscʹ͸ઃఆͰ͖ͳ͍ APIܦ༝Ͱར༻͢Δඞཁ͕͋Δ - ϝδϟʔͳϏϧυπʔϧ͸Custom transformer͕ઃఆՄೳ - e.g.

    ts-loader, awesome-typescript-loader, gulp-typescript, etc...
  46. Transformer - Custom transformerࣗମ͸௚઀ʮDXΛߴΊΔʯʹ͸د༩͠ͳ͍ - ੜ੒͞ΕΔ.jsίʔυʹ৵ऻ͢ΔͨΊɺΉ͠ΖUXʹӨڹ͢Δ͜ͱ΋

  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> `;
  48. Case 3-1. AoT - ͜ͷྨͷϑϨʔϜϫʔΫͰ͸ɺDSLݻ༗ͷதؒදݱʹςϯϓϨʔτΛί ϯύΠϧͯ͠࠶ར༻͢ΔΑ͏ʹઃܭ͞Ε͍ͯΔέʔε͕ଟ͍ - ͦͷ··ϒϥ΢βʹ͍࣋ͬͯ͘ͱɺੑೳΛྼԽͤ͞Δ͜ͱ΋ - ࣮ߦ࣌parseͷΦʔόʔϔου

    - parserͦͷ΋ͷؚ͕·ΕΔ͜ͱʹΑΓɺbundleαΠζ͕૿େ
  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Λఏڙ
  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); }
  51. Other use cases - ιʔείʔυதͷtype alias΍interfaceͷ৘ใΛ࠷ऴతͳ.jsϑΝΠϧʹ࣋ ͪग़͢͜ͱ΋Մೳ - https://github.com/angular/tsickle -

    TypeScriptͷܕ৘ใΛGoogle Closure CompilerͷJSDocʹม׵͢Δ
  52. Summary

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

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

    API - ASTͷಡΈॻ͖ʹ׳ΕͨΒTransformer API͸؆୯ - ASTղੳͷ஌ࣝ͸TypeScriptҎ֎Ͱ΋໾ʹཱͭʢe.g. ESLint rulesʣ
  55. TypeScript APIΛ࢖ͬͨ πʔϧ࡞Γ͸ָ͍ͧ͠

  56. Thank you !