mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-08-31 01:02:04 +08:00
fixed styleguide
This commit is contained in:
+1
-1
@@ -165,7 +165,7 @@ doc/build
|
||||
doc/schema.md
|
||||
doc/source/admin/config_logging_default_yaml.rst
|
||||
doc/source/dev/schema.md
|
||||
client/styleguide
|
||||
client/styleguide/dist
|
||||
|
||||
# webpack stats
|
||||
client/webpack-stats.json
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Component root for rendering inside the styleguide, injects vuex store and other common elements
|
||||
* into each component example section.
|
||||
*/
|
||||
|
||||
import store from "../src/store";
|
||||
|
||||
export default (previewComponent) => {
|
||||
return {
|
||||
store,
|
||||
render(h) {
|
||||
return h(previewComponent);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Functions for generating the sections for the style guide. Has the old style doc glob and a
|
||||
* recursive tree-walker that looks through the components folder and tries to arrange found
|
||||
* markdown docs in a tree.
|
||||
*/
|
||||
|
||||
const path = require("path");
|
||||
const glob = require("glob");
|
||||
const { humanize, titleize } = require("underscore.string");
|
||||
|
||||
/**
|
||||
* Gets the table of contents sections for documentation in the components folder.
|
||||
*
|
||||
* @param {string} rootPath absolute path to components folder
|
||||
* @return {Object} Rootnode, section map, childMap
|
||||
*/
|
||||
function getDocSections(rootPath, options = {}) {
|
||||
const { ignore = [], docSelector = "*.@(vue|md)" } = options;
|
||||
|
||||
const sections = new Map(); // absolute section directory path -> section object
|
||||
const childToParent = new Map(); // absolute child path -> absolute parent path
|
||||
|
||||
// create root node
|
||||
const rootNode = newSection(rootPath, ignore);
|
||||
sections.set(rootPath, rootNode);
|
||||
childToParent.set(rootPath, null);
|
||||
|
||||
// const singleGlob = path.join(rootPath, "**/*.(md|js|vue)");
|
||||
const selector = path.join(rootPath, "**", docSelector);
|
||||
const allFiles = glob.sync(selector, { ignore });
|
||||
|
||||
allFiles.forEach((file) => {
|
||||
const p = path.parse(file);
|
||||
|
||||
// intermediate dir names between this dir and root
|
||||
const relPath = path.relative(rootPath, p.dir);
|
||||
const midFolders = relPath.split(path.sep);
|
||||
|
||||
// build intermediate parent sections in the event that this is deeply-nested
|
||||
while (midFolders.length) {
|
||||
const sectionPath = path.join(rootPath, ...midFolders);
|
||||
if (!sections.has(sectionPath)) {
|
||||
// create new section
|
||||
const section = newSection(sectionPath, ignore);
|
||||
sections.set(sectionPath, section);
|
||||
// register relationship for tree-build later
|
||||
const parentPath = path.join(sectionPath, "..");
|
||||
childToParent.set(sectionPath, parentPath);
|
||||
}
|
||||
midFolders.pop();
|
||||
}
|
||||
|
||||
// add file to section now that it exists
|
||||
const section = sections.get(p.dir);
|
||||
if (section && file !== section.content) {
|
||||
section.children.add({
|
||||
name: titleize(humanize(p.name)),
|
||||
content: file,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// assemble recursive section tree under rootNode
|
||||
buildSectionTree(sections, childToParent);
|
||||
|
||||
// rootNode is the main result, returning the maps so the user has the option to manipulate the
|
||||
// tree before handing it over to the styleguide configs
|
||||
return { sections, childToParent, rootNode };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new section in the TOC from passed path.
|
||||
*
|
||||
* @param {string} dir absolute directory path
|
||||
* @return {Object} section object
|
||||
*/
|
||||
function newSection(dir, ignore = []) {
|
||||
const section = {
|
||||
name: titleize(humanize(path.basename(dir))),
|
||||
components: () => glob.sync(path.join(dir, "*.vue"), { ignore }),
|
||||
children: new Set(),
|
||||
sectionDepth: 1,
|
||||
};
|
||||
|
||||
// summary doc is readme/docs/index.md
|
||||
const summarySelector = path.join(dir, "@(readme|docs|index).md");
|
||||
const summaryDocs = glob.sync(summarySelector, { ignore, nocase: true });
|
||||
if (summaryDocs.length) {
|
||||
section.content = summaryDocs[0];
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the pair of maps into a nested object for use in the styleguide config script. Operates on
|
||||
* sections array by reference.
|
||||
*
|
||||
* @param {Map} sections Map of path -> section object
|
||||
* @param {Map} childToParent Map of childPath -> parentPath
|
||||
*/
|
||||
function buildSectionTree(sections, childToParent) {
|
||||
// add each child to its parent
|
||||
for (const [childPath, parentPath] of childToParent) {
|
||||
const child = sections.get(childPath);
|
||||
const parent = sections.get(parentPath);
|
||||
if (child && parent && parent.children) {
|
||||
parent.children.add(child);
|
||||
}
|
||||
}
|
||||
// convert child Sets to arrays now that de-dupe not required
|
||||
for (const section of sections.values()) {
|
||||
section.sections = Array.from(section.children).sort((a, b) => a.name.localeCompare(b.name));
|
||||
delete section.children;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// public function
|
||||
getDocSections,
|
||||
// exporting these in case user wants to tweak the result tree and rebuild it
|
||||
buildSectionTree,
|
||||
newSection,
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
Don't directly reference window.Galaxy in Vue components
|
||||
|
||||
You've got components. Components take props. Please pass in any values you might need from
|
||||
window.Galaxy as props and avoid referencing global Galaxy inside your components. I've even created
|
||||
basic providers which give you access to the Galaxy.config, current user, and current user
|
||||
histories. Please use them to retrieve your values, and bypass importing Galaxy altogether.
|
||||
|
||||
There are use-cases where the Backbone models update over time and we need to update some value
|
||||
inside Vue. Let me help you solve those problems instead of importing backbone models into Vue
|
||||
components. Usually the answer is a backbone event listener that updates some relevant Vuex store.
|
||||
|
||||
When writing a new component, your goal should be to replace old Galaxy functionality, not to
|
||||
repackage it in another format so we continue to have Galaxy's inherent problems, the most important
|
||||
of which is...
|
||||
@@ -0,0 +1,14 @@
|
||||
### Classes exist but they aren't as important as you may be used to.
|
||||
|
||||
You may come from a class-based programming background and think that your first step should be to
|
||||
make a class hierchy that does the thing you want. It doesn't help that Vue superficially looks like
|
||||
a class definition, (even though what you're really doing is configuring an Observable tree).
|
||||
|
||||
### Javascript is a pretty functional language.
|
||||
|
||||
Javascript is largely a functional language. Its class support is limited and less useful, and
|
||||
unless we switch to Typescript, we don't even have interfaces or typing, arguably the most useful
|
||||
parts of a class-based language.
|
||||
|
||||
In general, you will get more mileage out of javascript by embracing functional programming
|
||||
approaches because that is what Javascript is good at.
|
||||
@@ -0,0 +1,14 @@
|
||||
Did you know, jQuery is old enough to drive? It's old enough to get a driver's license. jQuery is a
|
||||
tool that was built to deal with inconsistencies in browsers that NO LONGER EXIST. In a couple
|
||||
years, jQuery will be voting, drinking, and capable of being tried as an adult.
|
||||
|
||||
If you think you need jQuery, you are mistaken. Please seek help from somebody in the wg-ui-ux
|
||||
workgroup. There is nothing jQuery can provide you that isn't already part of vanilla javascript or
|
||||
a standard well-tested 3rd party modern npm module.
|
||||
|
||||
But that's not even the main problem. jQuery injects global initializations into every page on the
|
||||
site whether you want it or not. jQuery leverages an outdated initialization paradigm which is
|
||||
hugely problematic when it comes to unit testing and module building.
|
||||
|
||||
One of our most important goals in redesigning Galaxy is the complete elimination of this library
|
||||
from our source, along with all its invasive plugins.
|
||||
@@ -0,0 +1,164 @@
|
||||
We are using components in two very distinct ways. The first, "normal", kind of component will
|
||||
probably look familiar to anybody whis is already passingly familiar with Vue. Here the relevant
|
||||
information comes in as properties, any internal variables get defined in "data", changes go out as
|
||||
events.
|
||||
|
||||
## Composition Example
|
||||
|
||||
```html static
|
||||
<!-- Use of a renderless component with a display component -->
|
||||
<DoodadProvider v-slot="{ doodad, saveDoodad }">
|
||||
<DoodadEditor :doodad="doodad" @update:doodad="saveDoodad" />
|
||||
</DoodadProvider>
|
||||
```
|
||||
|
||||
In this example, we've created a component whose job is to deal with loading and updating the doodad
|
||||
object. Notice that there is no markup inside the DoodadProvider other than the explicit renderless
|
||||
component we previously made, but you are free to putput whatever you want in there, accessing the
|
||||
doodad and saveDoddad properties as desired, as well as any other local data with the only
|
||||
restriction that Vue needs a single root element in which to render.
|
||||
|
||||
|
||||
## The Renderer
|
||||
|
||||
```html static
|
||||
<!-- DoodadEditor.vue, a simple "rendering" component -->
|
||||
|
||||
<template>
|
||||
<AutoComplete
|
||||
:options="options"
|
||||
:value="doodad.category"
|
||||
@select="saveCategory"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
doodad: { type: Object, required: true },
|
||||
options: { type: Array, required: true },
|
||||
},
|
||||
methods: {
|
||||
saveCategory(newCategory) {
|
||||
this.$emit('update:doodad', { ...this.doodad, category: newCategory });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
```
|
||||
|
||||
This component accepts a mandatory input object (doodad), lets the user play with a category prop,
|
||||
then emits a fresh object after it's done. So what, what's the big deal? The important part to walk
|
||||
away from this dumb example is the things that are NOT in this sample component.
|
||||
|
||||
This component doesn't save the data. This component doesn't make ajax calls, and this component
|
||||
doesn't mutate its props. What it does do is to allow the user to edit some object named "doodad"
|
||||
and emits a new fresh version of that doodad when it's done. (Note also that we are using the
|
||||
update:propname event syntax whenever possible [to facilitate .sync
|
||||
binds](https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)).
|
||||
|
||||
Whatever happens to that new object is somebody else's job. As soon as you tie the data management
|
||||
to the rendering, the re-usability of your components craters.
|
||||
|
||||
|
||||
## The Provider
|
||||
|
||||
As the opposite of the rendering component, a provider or renderless component, is pure lobic. It
|
||||
should not know or care what your renderer is going to do with the data it provides. It is simply a
|
||||
fancy way of configuring some data manipulation methods. This is one of the many ways of reusing
|
||||
functionality available in Vue. Some others are [Mixins](https://vuejs.org/v2/guide/mixins.html),
|
||||
[Provide/Inject](https://v3.vuejs.org/guide/component-provide-inject.html) and (in Vue3) [the
|
||||
composition API](https://v3.vuejs.org/guide/composition-api-introduction.html).
|
||||
|
||||
|
||||
```js static
|
||||
// DoodadProvider.js
|
||||
|
||||
import { loadDoodad, saveDoodad } from "./someAjaxQueryModule";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loaded: false,
|
||||
doodad: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveDoodad(newVal) {
|
||||
this.loaded = false;
|
||||
const newVal = await saveDoodad(newVal);
|
||||
this.doodad = newVal;
|
||||
this.loaded = true;
|
||||
},
|
||||
async loadDoodad(newVal) {
|
||||
this.loaded = false;
|
||||
const newVal = await loadDoodad(newVal);
|
||||
this.loaded = true;
|
||||
return newVal;
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.doodad = await loadDoodad();
|
||||
},
|
||||
render() {
|
||||
return return this.$scopedSlots.default({
|
||||
loaded: this.loaded,
|
||||
doodad: this.doodad,
|
||||
saveDoodad: this.saveDoodad
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here is an example of a simple possible renderless provider. The important part about this is the
|
||||
render() function which simply renders [one big default
|
||||
slot](https://vuejs.org/v2/guide/components-slots.html) and binds some of its own properties to that
|
||||
slot for use by downstream components.
|
||||
|
||||
## Unit Testing a renderless component
|
||||
|
||||
Testing of a component like our editor is pretty straightforward and follows the standard Vue
|
||||
guidelines. If you did a clean-enough job you won't even need to mock anything since all your data
|
||||
dependencies should be delivered via props.
|
||||
|
||||
But it's not so obvious how to unit-test a renderless provider. What do you check for? There's no
|
||||
mandatory markup, just one big empty slot.
|
||||
|
||||
```js static
|
||||
// Testing a renderless component
|
||||
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getLocalVue, waitForLifecyleEvent } from "jest/helpers";
|
||||
import DoodadProvider from "./DoodadProvider";
|
||||
|
||||
describe("A renderless component", () => {
|
||||
const localVue = getLocalVue();
|
||||
let wrapper;
|
||||
let slotProps;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = shallowMount(DoodadProvider, {
|
||||
localVue,
|
||||
// The mount fn allows you to hook into a slot for this very reason
|
||||
scopedSlots: {
|
||||
default(props) {
|
||||
slotProps = props;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// waits for "updated" Vue lifecycle hook to fire on the renderless
|
||||
// component. This is often good enough for waiting for
|
||||
// an initial ajax load to finish, for example
|
||||
await waitForLifecyleEvent(wrapper.vm, "updated");
|
||||
})
|
||||
|
||||
test("someProp", () => {
|
||||
const { someProp } = slotProps;
|
||||
expect(someProp).toExist();
|
||||
// ...more tests
|
||||
})
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
## Wrap access to browser-native resources in functions
|
||||
|
||||
Unit testing in a modern javascript application runs in node without a browser.
|
||||
@@ -0,0 +1,82 @@
|
||||
## Understand that a component is really just a fancy function
|
||||
|
||||
I'm not talking about how webpack turns it into a rendering function. That's obvious.
|
||||
|
||||
I mean conceptually, props come in (like arguments) and events go out (like the return statements).
|
||||
A component is a fancy kind of function that can keep emitting results and accept changing inputs
|
||||
over time. In truth it more closely resembles an Observable, but an observable is ALSO a slightly
|
||||
fancier kind of function.
|
||||
|
||||
If you just think of a component as thing that takes inputs and emits outputs you're well on your
|
||||
way to using them properly.
|
||||
|
||||
The worst components are ones that might as well just be a big single script that runs. That's zero
|
||||
percent better than the spaghetti we're working so hard to replace. That's just repackaging all the
|
||||
problems of the old imperative class-based legacy code.
|
||||
|
||||
|
||||
## Javascript is a pretty functional language. Classes exist but they aren't as important as you may be used to.
|
||||
|
||||
You may come from a class-based programming background and think that your first step should be to
|
||||
make a class hierchy that does the thing you want. It doesn't help that Vue superficially looks like
|
||||
a class definition, (even though what you're really doing is configuring an Observable tree).
|
||||
|
||||
But javascript is largely a functional language. Its class support is limited and less useful, and
|
||||
unless we switch to Typescript, we don't even have interfaces or typing, arguably the most useful
|
||||
parts of a class-based language.
|
||||
|
||||
In general, you will get more mileage out of javascript by embracing functional programming
|
||||
approaches because that is what Javascript is good at.
|
||||
|
||||
|
||||
## Get comfortable with events, limit your dependence on Vuex
|
||||
|
||||
New vue programmers are ok at handing props to components, but they rarely use events effectively
|
||||
(at first). As a result they end up using a lot of global state, a million little data props and
|
||||
relying on imperfect globalized tools like Vuex or other imported dependencies for every little
|
||||
variable.
|
||||
|
||||
Vuex has its uses, but not as many as you might think. Data persistence should be something that
|
||||
happens near the top of your component tree, not down in the guts.
|
||||
|
||||
Your first thought with a component should be: "How can I offload the handling of the results of
|
||||
this component to my caller?" The answer is usually going to be events. A component that simply
|
||||
accepts props and emits events can be re-used in more contexts than one that relies on an external
|
||||
global state to operate.
|
||||
|
||||
### Read up on .sync and v-model
|
||||
|
||||
They're just fancy shorthands for a prop / event handler combination. They are fundamentally no
|
||||
different from props and events, but the syntax is important to understand.
|
||||
|
||||
|
||||
|
||||
## Think carefully about what should really be in "data".
|
||||
|
||||
Most of good component design boils down to answering the following question: What do I want to put
|
||||
in data, computed, and props?
|
||||
|
||||
Data is the place where temp data goes that is not sensible to persist in Vuex or other
|
||||
application-wide global state, usually because its use is very specific to the operation of this
|
||||
particular component. There really should only be a few variables in data.
|
||||
|
||||
### Break down your own internal dependencies
|
||||
|
||||
Most components only need one or two variables in data. If you take the time to analyze your own
|
||||
internal dependency tree, you will probably find that almost everything can be written in terms of
|
||||
computed transformations on a small number of data and propertie
|
||||
|
||||
If you have more than a few variables you (a) aren't leveraging computeds and properties, or (b) are
|
||||
trying to implement too many features for just one component. It is important separate your concerns
|
||||
in components just like you do in any other kind of programming.
|
||||
|
||||
|
||||
|
||||
## Have a plan
|
||||
|
||||
Create a design plan and an abstraction for the way the guts of your component work. Think about the
|
||||
way data passes from parent components to children and back again. Break your component into
|
||||
sub-components just like you would break a class into sub-methods, the same exact principles apply.
|
||||
|
||||
Don't just dump a pile of spaghetti into a component, that is no better than the legacy code we are
|
||||
replacing.
|
||||
@@ -0,0 +1 @@
|
||||
# testdocs/a-file.md
|
||||
@@ -0,0 +1 @@
|
||||
# testdocs/another-file.md
|
||||
@@ -0,0 +1 @@
|
||||
# ignored-file.md
|
||||
@@ -0,0 +1 @@
|
||||
# some-loose-file.md
|
||||
@@ -0,0 +1 @@
|
||||
# loose file
|
||||
@@ -0,0 +1 @@
|
||||
# testdocs/abc/readme.md
|
||||
@@ -0,0 +1 @@
|
||||
# foo
|
||||
@@ -0,0 +1 @@
|
||||
# another loose file
|
||||
@@ -0,0 +1 @@
|
||||
# Loose file
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Tests the functions which build the sections for the styleguide.
|
||||
*/
|
||||
|
||||
const path = require("path");
|
||||
const { getDocSections } = require("../sections");
|
||||
|
||||
const getSectionByName = (sections, name) => sections.find((o) => o.name == name);
|
||||
|
||||
describe("getDocSections", () => {
|
||||
const ignore = [
|
||||
// ignoring a whole directory
|
||||
"**/ignored/*",
|
||||
// ignoring a file
|
||||
"**/ignore*",
|
||||
];
|
||||
const testDocRoot = path.join(__dirname, "sample-docs");
|
||||
const { rootNode } = getDocSections(testDocRoot, { ignore });
|
||||
|
||||
test("section generation", () => {
|
||||
expect(rootNode.name).toEqual("Sample Docs");
|
||||
expect(rootNode.sections.length).toEqual(6);
|
||||
expect(rootNode.content).toBeUndefined();
|
||||
});
|
||||
|
||||
test("nested subsection should appear even if no docs in intermediate folders", () => {
|
||||
const deepSection = getSectionByName(rootNode.sections, "Nested Folders");
|
||||
expect(deepSection.sections.length).toEqual(1);
|
||||
});
|
||||
|
||||
test("subdirectory with readme should register as summary", () => {
|
||||
const subsection = getSectionByName(rootNode.sections, "Has Readme");
|
||||
|
||||
// readme interpreted as content file and not as loose section
|
||||
// one other loose file
|
||||
expect(subsection.content).toContain("readme.md");
|
||||
expect(subsection.sections.length).toEqual(1);
|
||||
});
|
||||
|
||||
test("subdirectory with no readme file", () => {
|
||||
const subsection = getSectionByName(rootNode.sections, "No Summary File");
|
||||
|
||||
// should just see 2 folders no summary
|
||||
expect(subsection.content).toBeUndefined();
|
||||
expect(subsection.sections.length).toEqual(2);
|
||||
});
|
||||
|
||||
test("subdirectory with ignored file", () => {
|
||||
const subsection = getSectionByName(rootNode.sections, "Contains Ommitted File");
|
||||
expect(subsection.sections.length).toEqual(1);
|
||||
});
|
||||
|
||||
test("ignored subdirectory", () => {
|
||||
const ignoredSection = getSectionByName(rootNode.sections, "Ignored");
|
||||
expect(ignoredSection).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
### Beta History Panel Component Tree
|
||||
This is not intended to be a complete listing, but a general idea of how the components are intended
|
||||
to interact with each other.
|
||||
|
||||
```html static
|
||||
<CurrentHistoryPanel>
|
||||
<HistoryPanel :history="history">
|
||||
|
||||
<!-- for the right-hand side history we show some
|
||||
optional nav elements, can be ommitted for histories
|
||||
shown in multi-history view -->
|
||||
<slot:nav>
|
||||
<HistorySelector />
|
||||
<HistoryMenu />
|
||||
</slot:nav>
|
||||
|
||||
|
||||
<!-- if main history selected -->
|
||||
<History :history="history">
|
||||
|
||||
<!-- HCP does the heavy-lifting of mixing params, history, and
|
||||
scroll position to deliver the content for the scroller -->
|
||||
|
||||
<HistoryContentProvider :parent="history">
|
||||
<HistoryDetails />
|
||||
<HistoryMessages />
|
||||
<ContentOperations />
|
||||
<Scroller>
|
||||
|
||||
<!-- HistoryContentItem is a dynamic component that becomes
|
||||
either Dataset or DatasetCollection depending
|
||||
on the props passed to it -->
|
||||
|
||||
(<HistoryContentItem />)
|
||||
<Dataset />
|
||||
<!-- or -->
|
||||
<DatasetCollection />
|
||||
|
||||
</Scroller>
|
||||
</HistoryContentProvider>
|
||||
|
||||
</History>
|
||||
|
||||
|
||||
<!-- When a collection is selected for viewing, send in a
|
||||
breadcrumbs list of collections the user has selected -->
|
||||
|
||||
<CurrentCollection :selected-collections="breadcrumbs">
|
||||
<CollectionContentProvider :parent="selectedCollection">
|
||||
<CollectionNav />
|
||||
<Details />
|
||||
<Scroller>
|
||||
|
||||
<!-- Subdataset and Subcollection are similar to the Dataset
|
||||
and DatasetCollection ContentItem components, but mostly
|
||||
read-only since they are part of the collection-->
|
||||
|
||||
(<CollectionContentItem />)
|
||||
<Subdataset />
|
||||
<!-- or -->
|
||||
<Subcollection />
|
||||
|
||||
</Scroller>
|
||||
</CollectionContentProvider>
|
||||
</CurrentCollection>
|
||||
|
||||
</HistoryPanel>
|
||||
</CurrentHistoryPanel>
|
||||
```
|
||||
+45
-46
@@ -1,58 +1,57 @@
|
||||
const path = require("path");
|
||||
const glob = require("glob");
|
||||
const fs = require("fs");
|
||||
const merge = require("webpack-merge");
|
||||
const baseConfig = require("./webpack.config.js");
|
||||
const { getDocSections } = require("./docs/sections");
|
||||
const buildWebpack = require("./webpack.config.js");
|
||||
|
||||
const webpackConfig = baseConfig();
|
||||
function getWebpack() {
|
||||
const cfg = buildWebpack();
|
||||
|
||||
const fileLoaderTest = /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?.*$|$)/;
|
||||
// looks like our src plays with the webpack publicPath dynamically,
|
||||
// presumably to allow for dyamic loads, but this is a problem when
|
||||
// you're not outputting code to a non-standard location.
|
||||
// allowing this to happen breaks the styleguide.
|
||||
cfg.module.rules.push({
|
||||
test: /onload\/publicPath/,
|
||||
use: { loader: "ignore-loader" },
|
||||
});
|
||||
|
||||
const fileLoaderConfigRule = { rules: [{ test: fileLoaderTest, use: ["file-loader"] }] };
|
||||
return cfg;
|
||||
}
|
||||
|
||||
webpackConfig.module = merge.smart(webpackConfig.module, fileLoaderConfigRule);
|
||||
webpackConfig.output.publicPath = "";
|
||||
// TODO: Fix broken module imports before attempting to view in styleguidef
|
||||
const problemChildren = ["**/HistoryView.vue", "**/admin/DataManager/*", "**/LibraryFolder/*"];
|
||||
if (problemChildren.length) {
|
||||
console.warn("Not rendering styleguide for the following components:", problemChildren);
|
||||
}
|
||||
|
||||
webpackConfig.resolve.modules.push(path.join(__dirname, "src/style/scss"));
|
||||
function getSections() {
|
||||
// Style sections
|
||||
const docRootPath = path.join(__dirname, "docs/src");
|
||||
const { rootNode: docRoot } = getDocSections(docRootPath, { docSelector: "*.md" });
|
||||
const [design, styles] = docRoot.sections;
|
||||
delete docRoot.components;
|
||||
|
||||
const galaxyStyleDocs = [];
|
||||
glob.sync("./docs/galaxy-*.md").forEach((file) => {
|
||||
const name = file.match(/galaxy-(\w+).md/)[1];
|
||||
galaxyStyleDocs.push({ name: name, content: file });
|
||||
});
|
||||
// recursive component tree docs
|
||||
const cmpPath = path.join(__dirname, "src/components");
|
||||
const { rootNode: componentDocs } = getDocSections(cmpPath, { ignore: problemChildren });
|
||||
|
||||
const sections = [
|
||||
{
|
||||
name: "Galaxy styles",
|
||||
sections: galaxyStyleDocs,
|
||||
},
|
||||
{
|
||||
name: "Basic Bootstrap Styles",
|
||||
content: "./docs/bootstrap.md",
|
||||
},
|
||||
{
|
||||
name: "Components",
|
||||
// Components that are directories will get their own section
|
||||
sections: glob
|
||||
.sync("./src/components/*")
|
||||
.map((file) => {
|
||||
if (fs.lstatSync(file).isDirectory()) {
|
||||
return {
|
||||
name: path.basename(file),
|
||||
components: file + "/**/*.vue",
|
||||
};
|
||||
}
|
||||
})
|
||||
.filter((v) => v),
|
||||
// ...while top level components are handled here.
|
||||
components: "./src/components/*.vue",
|
||||
},
|
||||
];
|
||||
return [design, componentDocs, styles];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
webpackConfig,
|
||||
webpackConfig: getWebpack(),
|
||||
title: "Galaxy Client Resources",
|
||||
sections: getSections(),
|
||||
getExampleFilename(componentPath) {
|
||||
return componentPath.replace(/\.(vue|js)?$/, ".md");
|
||||
},
|
||||
require: [
|
||||
"./src/style/scss/base.scss",
|
||||
"./src/polyfills.js",
|
||||
// "./src/bundleEntries.js"
|
||||
],
|
||||
tocMode: "collapse",
|
||||
renderRootJsx: "./docs/root",
|
||||
styleguideDir: "./docs/dist",
|
||||
pagePerSection: true,
|
||||
sections,
|
||||
require: ["./src/style/scss/base.scss", "./src/polyfills.js", "./src/bundleEntries.js"],
|
||||
vuex: "./src/store/index.js",
|
||||
ignore: problemChildren,
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ module.exports = {
|
||||
rootDir: path.join(__dirname, "../../"),
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
roots: ["<rootDir>/src", "<rootDir>/tests/jest/standalone/"],
|
||||
roots: ["<rootDir>/src", "<rootDir>/tests/jest/standalone/", "<rootDir>/docs/"],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
Reference in New Issue
Block a user