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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  5. TypeScript and DX

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. 1. Code generation

    View full-size slide

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

    View full-size slide

  15. 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 full-size slide

  16. 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 full-size slide

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

    View full-size slide

  18. 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 full-size slide

  19. 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 full-size slide

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

    View full-size slide

  21. 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 full-size slide

  22. 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 full-size slide

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

    View full-size slide

  24. TS AST viewer

    View full-size slide

  25. 2. Editor extension

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  28. 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 full-size slide

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

    View full-size slide

  30. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  33. 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 full-size slide

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

    View full-size slide

  35. 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 full-size slide

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

    View full-size slide

  37. 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 full-size slide

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

    View full-size slide

  39. 3. Transformation

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  42. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  45. 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 full-size slide

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

    View full-size slide

  47. 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 full-size slide

  48. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide