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

Extends Developer Experience

893f54413c2bd9ba41d11d753aacaf2c?s=47 Yosuke Kurami
February 23, 2020

Extends Developer Experience

TypeScriptとDXのお話

893f54413c2bd9ba41d11d753aacaf2c?s=128

Yosuke Kurami

February 23, 2020
Tweet

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 !