Vue.js + ASP.NET Core + Webpack

Retour d'expérience, parce que j'ai galéré.

Je me suis lancé il y a peu dans un projet pour un ami, afin de l’aider à automatiser certaines tâches. Comme je suis une quiche en frontend, je me suis dit que c’était l’occasion d’essayer un framework JavaScript. J’ai mis de côté les deux poids-lourds Angular et React pour jeter mon dévolu sur Vue.js. Pourtant, j’ai galéré comme un veau à avoir un projet qui compile et qui soit fluide.

Je vais donc vous faire un retour d’expérience pour que, dans le cas où vous seriez dans la même situation que moi, vous puissiez moins galérer.

Créer le projet

Comme je l’ai dis, je voulais utiliser .NET Core. Je suis donc parti sur un back Web API et un front utilisant Vue.js et TypeScript. Pour le back, aucun problème. Seulement voilà, même avec Visual Studio 2017, il n’y a pas de template pour créer un front utilisant Vue.js. Bien sûr, il y a Angular et React, mais pas celui que je cherche.

Pas de soucis, on va chercher sur Internet. On tombe sur cette commande pour installer les templates SPA.

dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

Il y a bien un template Vue.js, mais celui-ci est complètement dépassé : Webpack 2 (aujourd’hui 4), jQuery 2 (aujourd’hui 3), TypeScript 2 (aujourd’hui 3), etc. Le projet compile, mais dès que je tente de mettre à jour les dépendances NPM, c’est la catastrophe, tout s’effondre. Le code s’exécute, mais je me prends une volée d’erreurs à l’exécution, dont même une recherche Google n’a pas été capable de m’aider.

// Des dépendances pas à jour.

{
  "name": "Site",
  "private": true,
  "version": "0.0.0",
  "devDependencies": {
    "@types/webpack-env": "^1.13.0",
    "aspnet-webpack": "^2.0.1",
    "awesome-typescript-loader": "^3.0.0",
    "bootstrap": "^3.3.6",
    "css-loader": "^0.25.0",
    "event-source-polyfill": "^0.0.7",
    "extract-text-webpack-plugin": "^2.0.0-rc",
    "file-loader": "^0.9.0",
    "isomorphic-fetch": "^2.2.1",
    "jquery": "^3.1.1",
    "style-loader": "^0.13.1",
    "typescript": "^2.2.1",
    "url-loader": "^0.5.7",
    "vue": "^2.2.2",
    "vue-loader": "^11.1.4",
    "vue-property-decorator": "^5.0.1",
    "vue-router": "^2.3.0",
    "vue-template-compiler": "^2.2.2",
    "webpack": "^2.2.0",
    "webpack-hot-middleware": "^2.12.2"
  }
}

Alors on continue et on tombe sur ce projet. Un nouveau template à installer. Mais celui-ci n’est que du JavaScript pur, ce qui ne m’arrange pas. Et en jetant un œil aux dépendances, elles ne sont pas vraiment plus à jour. Mince.

Je tombe sur cet article qui va sans doute m’aider. Alors je suis les instructions, mais l’article n’est pas clair, je galère, je n’y arrive pas. Je me tape toujours la même exception. :colere2:

Puis, après encore d’autres recherches, je tombe sur ce projet. Une lueur d’espoir se rallume. Je suis les instructions, je créé un projet pour tester, il marche, les dépendances ne sont pas trop vieilles, je suis sur le bon chemin.

Mon projet ne compile pas !

Je vais donc créer mon projet moi-même et m’inspirer de ce qu’a fait l’auteur du template pour compléter ce qui me manque et ainsi avoir un projet fonctionnel et personnalisé. Je créé d’abord un projet ASP.NET Core vide, que je vais remplir au fur et à mesure. Et, évidemment, je rencontre plusieurs erreurs que je ne voyais pas, même avec le template le plus à jour.

You are using the runtime-only build of Vue where the template compiler is not available.

Une recherche assez rapide m’indique une ligne à rajouter dans le fichier de configuration de Webpack. Un alias

module.exports = {
    ...
    resolve: {
        ...
        alias: {
            'vue$': 'vue/dist/vue.esm.js'
        }
        ...
    }
    ...
}
TypeScript : Cannot find module './App'.

Précisons que cette erreur a lieu alors même que le module est accessible et dans le même dossier que le fichier .ts en cours d’édition. Apparemment, TypeScript n’aime pas trop les fichiers .vue. On règle se problème en créant un fichier vue-shims.d.ts, à la racine du projet.

declare module "*.vue" {
    import Vue from "vue";
    export default Vue;
}

Et, comme j’utilise vue-loader dans sa dernière version, j’ai eu le droit à une belle erreur due à des modifications dans la v15 de ce package.

Vue loader was used without the corresponding plugin. Make sure to inclure VueLoaderPlugin in you wekpack config.

Bon, il faut donc importer ce plugin et puis l’ajouter à la liste correspondante.

const VueLoaderPlugin = require('vue-loader/lib/plugin');

...

module.exports = {
    ...
    plugins: [
            new VueLoaderPlugin(),
            ...
    ]
    ...
}

Maintenant, le projet compile et s’exécute, mais survient un problème qui m’embête : le Hot Module Replacement ne fonctionne qu’une seule et unique fois. Ce module permet de recharger les fichiers .vue (entre autres) au moindre Ctrl + S, au lieu d’un F5 ou d’une recompilation. Pourtant, sur l’autre projet, ça marche. Alors, où est le problème ?

Ce n’est pas le fichier Startup.cs, puisque le HMR est bien activé.

app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
    HotModuleReplacement = true
});

Peut-être qu’en changeant les ports de l’application ?

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:51192/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "vuets": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:51193/"
    }
  }
}

Décidément, d’où vient ce problème. Aucune des solutions que j’ai trouvé sur Internet n’a fonctionné pour moi. Suis-je condamné à m’en passer ? En désespoir de cause, je modifie mon fichier .csproj. Sait-on jamais…

Voici celui avant.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
    <TypeScriptToolsVersion>3.1</TypeScriptToolsVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" />
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
    <PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="2.2.0" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.0" />
  </ItemGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

  <Target Name="DebugRunWebpack" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('wwwroot\dist') ">
    <!-- Ensure Node.js is installed -->
    <Exec Command="node --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />

    <!-- In development, the dist files won't exist on the first run or when cloning to
         a different machine, so rebuild them if not already present. -->
    <Message Importance="high" Text="Performing first-run Webpack build..." />
    <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js" />
    <Exec Command="node node_modules/webpack/bin/webpack.js" />
  </Target>

  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
    <Exec Command="npm install" />
    <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />
    <Exec Command="node node_modules/webpack/bin/webpack.js --env.prod" />

    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="wwwroot\dist\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

</Project>

Et le voici après.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>


  <Target Name="DebugRunWebpack" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('wwwroot\dist') ">
    <!-- Ensure Node.js is installed -->
    <Exec Command="node --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />

    <!-- In development, the dist files won't exist on the first run or when cloning to
         a different machine, so rebuild them if not already present. -->
    <Message Importance="high" Text="Performing first-run Webpack build..." />
    <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js" />
    <Exec Command="node node_modules/webpack/bin/webpack.js" />
  </Target>

  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
    <Exec Command="npm install" />
    <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />
    <Exec Command="node node_modules/webpack/bin/webpack.js --env.prod" />

    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="wwwroot\dist\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

</Project>

Eh bien, aussi bizarre que ça soit, ça marche ! :ange:

Est-ce un Nuget supprimé ? Est-ce la ligne <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> qui empêchait le HMR de fonctionner ? J’avoue que je n’en sais pas grand chose. Par contre, maintenant, mon projet compile, s’exécute et le HMR est fonctionnel.

En conclusion

Arf, j’ai bien galéré. Autant le back, ça a roulé nickel chrome tout seul, autant le front j’en ai douillé comme pas possible. Et je suis entièrement d’accord avec @SpaceFox sur la folie furieuse de la communauté.

Y’a un moment faut arrêter de délirer. Tous les millions de packages pour une appli simple, chaque mise à jour qui peut potentiellement déprécier la moitié de ton code et donc te forcer à pleins de modification sans aucune valeur ajoutée, les erreurs cryptiques, les documentations pas à jour et donc un code marche chez ton voisin mais plus chez toi, etc.

Voilà, j’espère que si, vous aussi, galérez comme pas permis, j’ai pu vous aider un peu. Bonne journée bien zestueuse. :)



7 commentaires

J’avais créer un sujet pour un mini projet personnel et je me suis demandé si c’était l’occasion d’utiliser Javascript pour ce dernier. Malheureusement, je n’arrive toujours pas à voir l’intérêt de cette façon de développer un site web.

Je vois souvent un parti pour et un parti contre. Entre les deux est plus rare. Ça dépend peut être de l’expérience PHP, Python ou Ruby passé face à ceux ayant débuté dès le début avec Javascript ?

Malheureusement, je n’arrive toujours pas à voir l’intérêt de cette façon de développer un site web.

Helmasaur

J’étais dans la même position et le même flou que toi. Puis j’ai regardé un peu Vue.js et j’ai beaucoup aimé le fait de pouvoir organiser plus proprement mon code, avec chaque composant bien séparé dans son fichier .vue correspondant, avec son template, ses scripts, son style.

Et ce n’est pas parce qu’on utilise Vue.js qu’on doit faire du SPA. Si je me base sur l’exemple fourni avec le template, il y a un composant pour la page compteur, un pour l’accueil, un pour le menu latéral, etc.

Entre les deux est plus rare.

Relis l’article de SpaceFox, je pense qu’il répond en partie à cette question.

Si on trace une flèche :

site vitrine grand public ———————————-> "pure" application dans le navigateur (imaginons "Discord").

A gauche, tu as besoin d’une indexation propre, certainement d’une sémantique parfaite, et de "santé" (un truc vraiment clean, où quand tu vois la source dans le navigateur, c’est clean), et d’une page qui se charge vite, et qui fout la paix au navigateur du client. (exemple : ton CV).

Une des meilleurs façons de faire ça, c’est de demander au serveur de générer la page. Et sur le serveur d’utiliser un moteur de template, avec le moins de variables (placeholders) possibles.

A l’opposé à droite : une application de chat, ou application de gestion, etc. Là, c’est l’inverse : le référencement, l’indexation on s’en fiche. Et tu ne peux pas vraiment prévoir ce qui sera affiché sur le navigateur du client à l’avance. Les gens vont s’envoyer des messages, parfois le réseau va être coupé et les messages seront en attente d’envoi, parfois l’utilisateur va complètement changer de channel, etc.

Tout bouge, tout est dynamique. Et tes préoccupations principales deviennent :

  • l’expérience utilisateur (même si ça prend du temps de changer de channel par ce que le mec est en connexion Edge, je l’informe proprement que je suis en train de le connecter au nouveau channel, je vais chercher la liste des utilisateurs, l’affiche, s’il est connecté je vais lui dé-griser le champ pour taper son message, même si je n’ai pas encore récupéré l’historique des communications
  • faire tout ça avec un paradigme SIMPLE (un truc facile à comprendre, facile à tester.

Y’a de fortes chances que si tu essaies de coder mon exemple de "Discord web" avec un truc (genre jQuery) qui s’amuse à changer l’état des objets du DOM (display block, display none, etc.) tu vas devenir fou. Tu vas sans arrêt devoir repasser mentalement par tous les états possibles pour chercher des bugs, c’est impossible.

Les frameworks orientés SPA répondent à ces deux problématiques. Leur(s) mode(s) de packaging permet d’avoir une UX propre, de choisir si tu veux que l’affichage soit là, même si le réseau est coupé, (ou pas, si c’est trop lourd de tout charger et que le fragment de page est peu accessible). On va chercher à limiter les allers-retour avec le serveur, souvent parce qu’on appelle des APIs, et souvent parce que c’est pas toujours les notres.

Et de l’autre côté, qu’il s’agisse de Vue(x) ou React (redux), la grande idée c’est de se dire "à un état de mes données en mémoire, correspond un rendu à l’écran". Ce que j’écris dans mon code, c’est le lien entre données => affichage, et comment changer ces données.

On ne s’occupe plus de changer l’état du DOM, c’est le framework qui le fait à ta place. Et si l’application que tu cherches à développer par son temps à "bouger" quand l’utilisateur fait des actions, ou que quelque chose bouge à l’autre bout du monde (quelqu’un a envoyé un message sur le channel) là oui, c’est fortement conseillé de choisir une SPA, et du coup l’un des frameworks qui a été conçu pour.

Si tu te trouves au milieu de la flèche, c’est là que ça devient difficile à arbitrer, et comme tous les choix qu’on fait, il va falloir le faire "émotionnellement". Tu es certainement dans ce cas là.


EDIT : et merci informaticienzero pour le retour d’expérience. C’est vrai que souvent l’intégration des frameworks SPA dans d’autres frameworks (côté serveur justement, genre Rails, ou ASP.net) c’est un peu deux mondes qui s’entre-choquent. (les premières version d’Angular pour Ruby on Rails étaient un nid à problèmes). Du coup ouais, t’as pas mal d’erreur liées à webpack et son enfer de configuration, et à quelques soucis décrits par SpaceFox. Mais t’as quand même pas mal de soucis liés à l’intégration Vue <-> ASP.net, ça c’est un autre problème. Toutes les histoire de csproj et de Nuget et de gros XML que tu nous as montrés, rien à voir avec webpack pour le coup :)

+6 -0

Non, je me sers quasi-exclusivement que de Twitter pour faire de la veille, et j’imagine que pas mal de gens que je follow sont abonnés à ce reddit oui, sans doute :)

Mais c’est bon à savoir, j’irai chercher l’information à la source maintenant ! Merci

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte