pahen / madge

Create graphs from your CommonJS, AMD or ES6 module dependencies
MIT License
8.73k stars 311 forks source link

Support single file component (VueJS) #122

Open abou7mied opened 6 years ago

abou7mied commented 6 years ago

I'm using VueJS and single file components (.vue files) which have dependencies but madge shows that .vue files has no deps.

The file structure is like that

<template>
...
</template>
<style>
...
</style>
<script>
  import file from './file.js';
...
</script>
mrjoelkemp commented 6 years ago

Thanks for contributing!

This is an interesting case. It looks like an html parser (or a naive regex if possible) could be used to grab the imports out of inlined script tags. This could be made part of precinct for a .vue extension, but it would be nice to plug that in as a config from madge instead of precinct ballooning for all new frameworks.

This isn't a trivial change, but possible.

abou7mied commented 6 years ago

Maybe you can implement an interface for extracting js code for any extension and add option like --extract-from-extension vue and madge will search for file with name extractor-vue.js

module.exports = function (contents, callback) {
  callback("text", contents.match(/something/));
  // in case of referring files like using src attribute
  callback("refer", "./referred-file.js");
}

and call the interface, maybe you can create extractors directory in the repo which have predefined extractors for popular frameworks.

I forgot to indicate in previous comment that the script tag may refer to another file using the src attribute so the return of the interface maybe text or reference

It's just an idea maybe you have a good one.

abou7mied commented 6 years ago

I asked in Vue support about how to get script contents and they replied there are vue-template-compiler

const compiler = require('vue-template-compiler')
const output = compiler.parseComponent(`<script>import file from './file'</script>`, {pad: 'line'});

console.log("output.script", output.script);
/*
output.script { type: 'script',
  content: 'import file from \'./file\'',
  start: 8,
  attrs: {},
  end: 33 }
*/

So now we can get the contents of the script tag and src attribute if it exists.

I think make an interface for madge to handle files with custom extensions is a good idea we can make one extractor madge-extractor.js file and it should handle multiple extension

module.exports = (extension, contents, callback) => {
  let isReference = false
  if (extension === "vue") {
    const compiler = require('vue-template-compiler')
    const output = compiler.parseComponent(contents, {pad: 'line'})
    if(output.script){
      isReference = output.script.attrs.src;
      if(!isReference)
        contents = output.script.content
    }
  }

  /*  callback(isReference?, reference|contents) */
  callback(isReference, isReference ? isReference : contents);

}
madge app.js --extensions js,vue --extractor madge-extractor.js
mrjoelkemp commented 6 years ago

Thanks for digging into this. Can regular components have the .vue extension or only these special template files?

If only the special case applies, then the path forward is similar to how we want to add TypeScript support #64. You need the code you wrote in precinct. You may not need to modify filing-cabinet since it defaults to an es6 resolver. Although, if you're using webpack, you may or may not (on mobile, sorry I can't verify) need to support the additional .vue extension in the config for enhanced-resolve.

I'm open to reviewing PRs for the effort. A good first step is to hack precinct and maybe filing-cabinet within madge's node_modules (or npm link if you're saavy) to get a proof of concept together.

pahen commented 6 years ago

Sounds like a good time to pick up the extension/plugin support again :)

How about something like how the webpack loaders work? Would that be suitable for precinct @mrjoelkemp? See https://webpack.js.org/concepts/loaders/

mrjoelkemp commented 6 years ago

That sounds good. Would madge be the supplier of the html detective and partial-resolver (like the resolvers in filing-cabinet)? Or would you pass that off to users of madge to supply their own detective/resolver combo (i.e., language pack)?

mrjoelkemp commented 6 years ago

Is it fine to have the most recently registered language pack win in the case of conflicts (i.e., when folks override an existing file extension's language pack)? Or should it get chained (like a linked list within a hashmap) on conflicts (where every registered language pack for that extension gets run on the original source?

Christopher-Wirt commented 5 years ago

Just a bump here to ask about progress. Madge is a pretty exciting project, but my repo has a mix of vue and JSX files.

Enity commented 5 years ago
const recursive = require('recursive-readdir');
const replace = require('replace-in-file');
const { extname } = require('path');
const { readFile, writeFile, unlink } = require('fs');
const { promisify } = require('util');

const readFileAsync = promisify(readFile);
const unlinkAsync = promisify(unlink);
const writeFileAsync = promisify(writeFile);

async function main(path = './') {
    const files = await getAllFiles(path);
    await Promise.all(files.map(file => replaceVueImportsWithJs(file)));

    const vueFiles = getVueFiles(files);
    await Promise.all(vueFiles.map(file => replaceVueFilesWithJs(file)));
}

async function getAllFiles(path) {
    return await recursive(path);
}

function getVueFiles(files) {
    return files.filter(f => f.includes('.vue'));
}

async function replaceVueImportsWithJs(file) {
    await replace({
        files: file,
        from: /\.vue/gm,
        to: '.js',
    });
}

async function replaceVueFilesWithJs(vueFile) {
    const script = await extractScript(vueFile);
    const newFileName = vueFile.replace(extname(vueFile), '.js');
    await unlinkAsync(vueFile);
    return writeFileAsync(newFileName, script, 'utf8');

    async function extractScript(file) {
        const content = await readFileAsync(file, 'utf8');
        return content.split('<script>')[1].split('</script>')[0];
    }
}

main('./src');

I just wrote a small script, which replaces all vue files and their imports with js) its a hack but solves the problem

ATTENTION

This script edits and deletes original files, i just use git to bring changes back

Mk-Etlinger commented 5 years ago

@Enity If I could upvote you more, I def would. Thanks for your quick and dirty script =) It helped me handoff a mess of a Vue app to another Dev. Cheers!

Also, love this project. It's incredible!

maxim-usikov commented 4 years ago

any news?

songlairui commented 4 years ago

I create a polyfill for vue project.

gist: https://gist.github.com/songlairui/287eaac29cda65cd5f1f7471b6661503 demo: https://github.com/songlairui/demo-madge-vue

image

grokpot commented 4 years ago

FYI, Nuxt.js just released the Components Module which I anticipate will grow in popularity. Whoever takes up this issue should keep that in mind. @Atinux maybe you know someone in the Vue community who would be interested in getting Madge and Vue to play together :)

bailnl commented 3 years ago

any news?

reuwi commented 3 years ago

Three years passed, has any progress...?

mrjoelkemp commented 3 years ago

@reuwi please feel free to submit a PR.

airene commented 3 years ago

I create a polyfill for vue project.

gist: https://gist.github.com/songlairui/287eaac29cda65cd5f1f7471b6661503 demo: https://github.com/songlairui/demo-madge-vue

image

I think it's a good way then @enity's method, when I use madge 4.0.2 , songlairui's polyfill can't work,it confuse me one day.

finally,I found madge add precinct to dependencies since 3.9.x,but not use it ,the reason is when dependency-tree require(precinct) and then polyfill require(precinct) ,these are two 'instance' of precinct.

I had to use const precinct = require('./node_modules/dependency-tree/node_modules/precinct') to keep this ployfill working.

By the way,Madge is great tools for check project's file relations, and why does madge add precinct dependence, madge dependence dependency-tree has depend on precinct yet? @pahen @mrjoelkemp

Thanks a lot!

Havunen commented 1 year ago

I needed this feature and implemented the support for Vue, I can create PRs soon

Havunen commented 1 year ago

I created a new detective vue package to handle vue single file components https://github.com/Havunen/detective-vue2

Created PR to node-precinct https://github.com/dependents/node-precinct/pull/112

Support for vue single file components to node-filing-cabinet https://github.com/dependents/node-filing-cabinet/pull/111

Then new version of precinct and node-filing-cabinet needs to be published and possibly bump the dependencies here: https://github.com/dependents/node-dependency-tree

Then finally PR could be created to add support to Madge

Havunen commented 1 year ago

@mrjoelkemp can you review the PRs please, it would be nice for madge to support vue files 😀

mikeslattery commented 1 year ago

This is way out of scope, but a tree-sitter parser would be more robust and able to handle all of kinds of alternate syntaxes, such as vue, svelte, tsx and non-js files such as html, pug, postcss and mixed variants such as svelte-pug, vue-scss-typescript.

crystalfp commented 10 months ago

Any news? This addition will be really useful. I'm running madge 6.1.0 Thanks! mario

Havunen commented 10 months ago

Can you please merge the PRs? @pahen @XhmikosR

frankykubo commented 7 months ago

Is this still active?

Havunen commented 7 months ago

Madge has dependencies to these packages which have pending PRs https://github.com/dependents/node-filing-cabinet/pull/111 https://github.com/dependents/node-precinct/pull/124

freemedom commented 7 months ago

For vue3, Enity's script needed some changes due to typescript support such as Githubissues.

  • Githubissues is a development platform for aggregating issues.