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

    View Slide

  2. About me
    - Yosuke KuramiʢGitHub, Twitter: @Quramyʣ
    - Web frontend developer
    - 5೥͘Β͍TypeScript΍ͬͯ·͢

    View Slide

  3. View Slide

  4. TypeScript APIΛ࢖ͬͨ
    πʔϧ࡞Γ͸ָ͍ͧ͠

    View Slide

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

    View Slide

  6. TypeScript and DX

    View Slide

  7. What’s DX ?
    - ͦ΋ͦ΋Developer ExperienceͬͯͳΜͩΖ͏
    - ΋ͱ΋ͱ͸User ExperienceʹΠϯεύΠΞ͞Εͨݴ༿
    - ʮ։ൃऀ͕ετϨεແ͘γεςϜΛ։ൃɾอकͰ͖Δ͜ͱʯ
    - ࣗಈԽɺ෼͔Γ΍͍͢APIυΩϡϝϯτɺetc...

    View Slide

  8. Bad DX example
    - ࢼ͠ʹʮDX͕ѱ͍ঢ়ଶʯΛߟ͑ͯΈΔ
    - ࣍ͷJavaScriptͷίʔυ͕͋ͬͨͱͯ͠ɿ
    function fn(someData) {
    return data.response;
    }

    View Slide

  9. Bad DX example
    - ͜ͷίʔυͷةݥੑ:
    - data is undefined
    - response͸dataͷkeyͱͯ͠ଘࡏ͍ͯ͠Δͷ͔ʁ
    function fn(someData) {
    return data.response;
    }

    View Slide

  10. Bad DX example
    - ΋͠ؾ෇͔ͣʹຊ൪ྲྀग़ͯ͠ো֐ʹͳͬͨͱ͢Δͱɿ
    - Өڹௐࠪ
    - hot fixͷ࡞੒/ϨϏϡʔ/σϓϩΠ
    - ৼΓฦΓ
    - ਺ߦͷमਖ਼Ͱ͋ͬͯ΋ɺ࣌ؒͱMP͕େྔʹ࡟ΒΕΔ

    View Slide

  11. With TypeScript
    - TypeScriptͰΧόʔͰ͖Δ͜ͱ:
    - ੩తղੳʹΑΔΤϥʔͷࣄલݕ஌
    - ར༻ՄೳͳϓϩύςΟͷࣗಈิ׬
    - etc…
    - ʮόάΛগͳͤ͘͞Δʯͱ͍͏ΞϑΥʔμϯεΛܗ੒͍ͯ͠Δ
    function fn(someData: DataType) {
    return someData.response;
    }

    View Slide

  12. Predictability
    - TypeScript͕JavaScriptΤϯδχΞʹ΋ͨΒͨ͠΋ͷɿ༧ଌՄೳੑ
    - TypeScripterʹͱͬͯʮDXΛ֦ு͢Δʯͱ͸ɿ
    - ༧ଌՄೳͳྖҬΛ޿͛Δ͜ͱ

    View Slide

  13. The unknown
    - TypeScript͕༧ଌͣ͠Β͍ྖҬ
    - JavaScriptҎ֎ͷΤίγεςϜͱͷ࿈ܞ
    - e.g. API req/res, database access,
    CSS / HTML template, etc...
    - ೗ԿʹunknownΛݮΒ͔͕͢DX޲্ͷ伴

    View Slide

  14. 1. Code generation

    View Slide

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

    View Slide

  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 () => (

    Title

    );

    View Slide

  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 () => (

    Title

    );
    /* Card.css.d.ts */
    declare const styles: {
    readonly "card": string;
    readonly "title": string;
    };
    export = styles

    View Slide

  18. Case 1-2. GraphQL data type
    - GraphQLͷಛ௃
    - ੩తʹܕ෇͚͞ΕͨSchema͕ଘࡏ͢Δ
    - ʢSchemaͰڐ༰͞ΕΔൣғͰʣࣗ༝ʹσʔλΛ໰͍߹ΘͤՄೳ

    View Slide

  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({ query: myQuery });
    console.log(data?.viewer?.repositories);

    View Slide

  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;
    };
    };
    };

    View Slide

  21. Code generation flow
    ίʔυੜ੒ͷྲྀΕɿ
    - ผݴޠΛղੳͯ͠ߏ଄Խ͞Εͨ৘ใΛಘΔ
    - e.g. GraphQL Schema, CSS Selector, Flow Type (Babylon)
    - ߏ଄Խ৘ใΛݩʹTypeScriptͷASTʢந৅ߏจ໦ʣΛ࡞Δ
    - ASTΛςΩετදݱʹͯ͠ɺϑΝΠϧͱͯ͠อଘ͢Δ

    View Slide

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

    View Slide

  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);

    View Slide

  24. Tips
    - TypeScript ASTͱίʔυͷؔ܎ΛݟΔʹ͸AST viewer͕͓͢͢Ί
    - https://ts-ast-viewer.com
    - ASTΛੜ੒͢ΔͨΊͷίʔυεχϖοτ΋දࣔͯ͘͠ΕΔ

    View Slide

  25. TS AST viewer

    View Slide

  26. 2. Editor extension

    View Slide

  27. Where can I check ?
    - ࠓ೔ͷΩʔϫʔυ͸ʮ༧ଌՄೳੑʯ
    - Ͳͷ࣌఺ͰΤϥʔΛ༧ଌ͢Δʁ CI or Git client hook or ...
    - ૣظʹ༧ଌͰ͖Ε͹Ͱ͖Δ΄ͲʮDX͕ߴ͍ʯ͸ͣ
    - Α͠ΤσΟλͰ΍ͬͯ͠·͓͏

    View Slide

  28. Language service plugin
    - Language serviceɿΤσΟλʹΤϥʔ৘ใ΍ิ׬ީิΛಧ͚Δ໾ׂ
    ʢVSCͰαΫαΫίʔυ͕ॻ͚Δͷ΋ίΠπͷ͓ӄʣ
    - Language serviceʹ͸Ϣʔβʔಠࣗͷplugin͕ઃఆՄೳ

    View Slide

  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"
    }
    ]
    }
    }

    View Slide

  30. View Slide

  31. Case 2-2. ESLint
    - https://github.com/Quramy/typescript-eslint-language-service
    -

    View Slide

  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

    View Slide

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

    View Slide

  34. Advantages of language service
    - TypeScript server (a.k.a. tsserver) ͷԸܙ
    - RopeʹΑΔString࣮૷΍ɺ૿෼parseͳͲߴ଎ͳج൫
    https://quramy.github.io/ts-server-side-anatomy/
    - ΤσΟλଆͰ֦ுΛ࡞ΔΑΓ΋ߴ଎ͳΠϯλϥΫγϣϯΛ࣮ݱՄೳ
    - ଎͚Ε͹଎͍΄Ͳྑ͍

    View Slide

  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 });

    View Slide

  36. How to create a plugin ?
    Language serviceͷ֦ுϙΠϯτʢҰ෦ൈਮʣɿ
    getCompleteAtPosition ࣗಈิ׬ީิͷฦ٫
    getSemanticDiagnostics Τϥʔ৘ใͷฦ٫
    getDefinitionAtPosition ఆٛՕॴ΁δϟϯϓ
    getQuickInfoAtPosition πʔϧνοϓ

    View Slide

  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,
    };
    }

    View Slide

  38. Plugin method code flow
    - ϑοΫͨ͠ϝιουͷதͰɺTypeScript ASTΛղੳ͢Δ
    - ྫ:
    - GraphQLͷέʔεɿTemplate string literalΛநग़͠ɺgraphql-jsͰղ
    ੳޙɺ݁ՌΛฦ٫͢Δ
    - ESLintͷέʔεɿTypeScript ASTΛestreeʹม׵͠ɺlinterΛݺͼग़͢

    View Slide

  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);
    }

    View Slide

  40. Let’s create your plugin
    - TypeScript language service plugin͸ͦͦ͜͜ϚχΞοΫͳ࢓૊Έ͚ͩ
    Ͳɺ΋ͬͱ޿·ͬͯཉ͍͠
    - TypeScriptؔ࿈ͷVSC֦ு࡞੒Λߟ͍͑ͯΔͷͰ͋Ε͹ɺlanguage
    service plugin΋ݕ౼ͯ͠Έͯ

    View Slide

  41. 3. Transformation

    View Slide

  42. I talked...
    Code generation: Writing AST Editor extension: Reading AST
    TypeScriptίʔυͷॻ͖׵͑
    Custom transformation

    View Slide

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

    View Slide

  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

    View Slide

  45. Caveat
    - Custom transformer͸tscʹ͸ઃఆͰ͖ͳ͍
    APIܦ༝Ͱར༻͢Δඞཁ͕͋Δ
    - ϝδϟʔͳϏϧυπʔϧ͸Custom transformer͕ઃఆՄೳ
    - e.g. ts-loader, awesome-typescript-loader, gulp-typescript, etc...

    View Slide

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

    View Slide

  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`
    Hello, ${name}
    `;

    View Slide

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

    View Slide

  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Λఏڙ

    View Slide

  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);
    }

    View Slide

  51. Other use cases
    - ιʔείʔυதͷtype alias΍interfaceͷ৘ใΛ࠷ऴతͳ.jsϑΝΠϧʹ࣋
    ͪग़͢͜ͱ΋Մೳ
    - https://github.com/angular/tsickle
    - TypeScriptͷܕ৘ใΛGoogle Closure CompilerͷJSDocʹม׵͢Δ

    View Slide

  52. Summary

    View Slide

  53. Summary
    - TypeScriptͰ֫ಘ͢ΔDX: ʮ༧ଌՄೳੑʯ
    - TypeScript APIͰ༧ଌՄೳੑΛఈ্͛͢Δ
    - .tsίʔυΛੜ੒ͯ͠unknownʹܕΛ༩͑Δ
    - .tsίʔυΛղੳͯ͠ΤσΟλʹ৘ใΛಧ͚Δ
    - ղੳ/ੜ੒ͷ࢓૊ΈΛ࠶ར༻͢Ε͹ɺύϑΥʔϚϯε޲্ʹܨ͕Δ͜ͱ΋

    View Slide

  54. Getting started
    - TypeScriptͷAPI͸͍ͬͺ͍͋Δ͚Ͳʢࠓ೔঺հͨ͠ͷ΋͘͝Ұ෦ʣ
    - ·ͣ͸ASTʹ׳ΕΔͷ͕Φεεϝ
    - ࣍ʹTypeCheckerͳͲͷCompiler APIͱLangage Service API
    - ASTͷಡΈॻ͖ʹ׳ΕͨΒTransformer API͸؆୯
    - ASTղੳͷ஌ࣝ͸TypeScriptҎ֎Ͱ΋໾ʹཱͭʢe.g. ESLint rulesʣ

    View Slide

  55. TypeScript APIΛ࢖ͬͨ
    πʔϧ࡞Γ͸ָ͍ͧ͠

    View Slide

  56. Thank you !

    View Slide