Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: User Config file integration #53

Merged
merged 26 commits into from
Apr 30, 2019
Merged

Conversation

hutchgrant
Copy link
Member

Related Issue

Resolves #40

Summary of Changes

  • reads from greenwood.config.js in root of app directory
  • current accepted keys:
{
    "source": "src",
    "publicPath": "/",
    "devServer": {
        "port": 1984,
        "host": "localhost"
    },
    "meta": {}
}
  • integrates with init.js to set the userworkspace path. "source" is prepended with current working directory
  • integrates with webpack configs
  • added tests for when using greenwood config file

Signed-off-by: HutchGrant <h.g.utchinson@gmail.com>
Copy link
Member

@thescientist13 thescientist13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💥

Nice job! A little beyond the scope, but the changes seem to work well together and you documented them nicely with unit tests, so I think it's OK to set this a base level for this feature. and it's important to reward ambition too, can't always be super sticklers I suppose. 😄 🏆

Just left a couple minor feedback points for review. And for going forward, we'll want to try and track each new config feature we want to add as a new issue.

@@ -1,29 +1,30 @@
require('colors');
const initConfig = require('./config');
const initContext = require('./init');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not for this issue, but maybe we should create an issue to change init.js to context.js?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree with that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done
#57

@@ -0,0 +1,9 @@
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we set the indentation to 2 here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea I don't know why my editor defaults to 4, have to get around to changing that.

test/setup.js Outdated
testApp: path.join(__dirname, 'fixtures', 'mock-app', 'src')
testApp: path.join(__dirname, 'fixtures', 'mock-app', 'src'),
userCfgPath: path.join(__dirname, 'fixtures', 'greenwood.config.json'),
userCfgRootPath: path.join(__dirname, '..', 'greenwood.config.json')
Copy link
Member

@thescientist13 thescientist13 Apr 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how come we are using this second config file? The names are really close and hard to distinguish. Maybe we just need more fixtures?

My comment here about creating "cases" of tests might be an appropriate justification.

Copy link
Member Author

@hutchgrant hutchgrant Apr 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

second config is just the hardcoded path to where the config will be copied. Hence: userCfgRootPath

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, so basically for mocking the user's project root, where their own greenwood.config.json would be located?

@@ -160,4 +160,67 @@ describe('building greenwood with error handling for app and page templates', ()
await fs.remove(CONTEXT.scratchDir);
});

});

describe('building greenwood with user provided config file', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the intent is to also test with a nested directory output, lets add it to the describe description, otherwise I think we can drop those additional tests for nested pages since they are already covered above, and if our architecture is sound, anything that breaks nested directories here should in theory also break it up there, which is a better logical block to solve it in.

In essence, we can try and have tests that take advantage of other tests already testing for specific outputs, so tests can have minimal overlap while still breaking each other if something goes wrong when making a change.

Copy link
Member Author

@hutchgrant hutchgrant Apr 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They aren't covered under this scenario, thats why they are there. In theory they should break earlier in the tests, but not necessarily with all the tests.

Every single situation the public directory, js bundle, hello, nested, need to be tested.

When the source directory changes(which it does as per this test when a config file is copied), everything has to be retested or at least partially.

For example, had we tested the index route in the nested pages describe here(using user workspace mock-app), we would have caught the error for #54 which I just found today because one of the duplicate files which was in the correct path wasn't serialized.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the source directory changes(which it does as per this test when a config file is copied), everything has to be retested or at least partially.

Right good point.

And I think that's a good approach to take here, as for changing something like the userWorkspace, essentially we would want to run everything from the beginning.

One thing that isn't so clear here though, and maybe this could just be made more clear in a describe or it is the specific configuration used and making sure to assert on those specific values somehow?

I would expect to see specific tests to cover each config API being supported (userWorkspace, devServer), even if it means recreating a new config file or amending dynamically in some way to make it as explicit as possible, since we should make sure that if the user provides supported configuration items

  • sourceDirectory
  • devServer
  • etc

That we call those out specifically in our testing somehow, like though

  • return value of initConfig
  • output of file paths after running build
  • webpack config?

Although some cases may have some overlap, I think it's OK if it means that we can define and test the critical paths and outputs of a given feature / workflow / etc for the project if it ensures our tests can be as faithful to how a user would also setup / run our code. Providing a great and accurate development / testing environment we'll provide us much more grounded and confident in the usefulness and coverage of our tests.

A little duplication for the sake of clarity and setup is a positive tradeoff / investment in my book 👍

I always like to think to myself, the convenience in making build tools is to the user, not the maintainer.

@thescientist13 thescientist13 added the RFC Proposal and changes to workflows, architecture, APIs, etc label Apr 26, 2019
Copy link
Member

@thescientist13 thescientist13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So sorry, for just catching this

Are we using json or js? In the code it's the former, in the README it's the latter.

I think I would prefer .js since if we want to add more build time features (like webpack plugins, or anything dynamic really) it would only be possible in a JS file.

Plus, a JS file can be linted. 👌

test/setup.js Outdated
testApp: path.join(__dirname, 'fixtures', 'mock-app', 'src')
testApp: path.join(__dirname, 'fixtures', 'mock-app', 'src'),
userCfgPath: path.join(__dirname, 'fixtures', 'greenwood.config.json'),
userCfgRootPath: path.join(__dirname, '..', 'greenwood.config.json')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, so basically for mocking the user's project root, where their own greenwood.config.json would be located?

@hutchgrant
Copy link
Member Author

hutchgrant commented Apr 26, 2019

.js doesn't lint properly with json formatting

You would have to module.exports = {} which is ugly to ask a user to do that.

@thescientist13
Copy link
Member

thescientist13 commented Apr 26, 2019

right, you need to export it as an object

module.exports = {
  source: 'src2',
  /* etc */
}

@hutchgrant
Copy link
Member Author

hutchgrant commented Apr 26, 2019

but is that really the simplest config file for the user?

module.exports = {
  source: 'src2',
  publicPath: '/',
  devServer: {
    port: 1984,
    host: 'localhost'
  },
  meta: {}
};

instead of

{
  "source": "src2",
  "publicPath": "/",
  "devServer": {
    "port": 1984,
    "host": "localhost"
  },
  "meta": {}
}

test/setup.js Outdated
const context = {
...ctx,
userSrc: path.join(__dirname, '..', 'src'), // static src
userTemplates: path.join(__dirname, '..', 'src', 'templates'), // static src/templates for testing empty templates dir, redundant in #38
testApp: path.join(__dirname, 'fixtures', 'mock-app', 'src')
testApp: path.join(__dirname, 'fixtures', 'mock-app', 'src'),
userCfgPath: path.join(__dirname, 'fixtures', 'greenwood.config.js'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would the mock config make sense being located in the mock-app folder?

Then testApp could be used to get to the paths for the config too. I think in general we will probably want to revisit this for #56 since I want to make sure we are very precise with the setup / teardown of our tests. In case based development, we'll probably favor composition over inheritance.

Maybe each test case can provide it's own custom context to TestSetup to make sure this setup function doesn't grow too large, handle too many cases, and add too much coupling to our tests.

Copy link
Member Author

@hutchgrant hutchgrant Apr 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know to me that seems like more code than simply adding variables where needed but I can see how that could be beneficial. Do you have an idea how to go about that?

config could be within mock-app sure, but then to use test-app we'd have to modify it from src and either:

  • a) hardcode a path within the test itself for src and greenwood.config.js or
  • b) add an additional context variable specifically for src.

We could do this a hundred times and continue to adjust the tests but I'd prefer to do it after these changes have been applied. So that I can move on this week, in addition to refactoring all these tests at once, not just specific for one single PR or another PR. Why not deal with it at once? This too would be a large change as well. This would actually reduce the amount of work, as the process of merging those changes, testing, then reviewing those changes, with the other PRs would take longer than doing it at once.

The point is while I'm waiting for it to be reviewed, whatever I do to the tests, I want to be able to work on something else. Which I can't, if we hold this back because of preferred organization of test variables.

Copy link
Member

@thescientist13 thescientist13 Apr 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is while I'm waiting for it to be reviewed, whatever I do to the tests, I want to be able to work on something else. Which I can't, if we hold this back because of preferred organization of test variables.

I didn't say / imply that though I don't think? I did say..

I think in general we will probably want to revisit this for #56

So anyway, I'm just trying to chat about the code in front of me, while it is in front of me is all. Please don't feel bad if I talk or ask a lot of questions in PRs, I'm just trying to understand the decision making process from your POV so I can best understand that and try and make equally informed decisions for when that time comes.

@@ -160,4 +160,67 @@ describe('building greenwood with error handling for app and page templates', ()
await fs.remove(CONTEXT.scratchDir);
});

});

describe('building greenwood with user provided config file', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the source directory changes(which it does as per this test when a config file is copied), everything has to be retested or at least partially.

Right good point.

And I think that's a good approach to take here, as for changing something like the userWorkspace, essentially we would want to run everything from the beginning.

One thing that isn't so clear here though, and maybe this could just be made more clear in a describe or it is the specific configuration used and making sure to assert on those specific values somehow?

I would expect to see specific tests to cover each config API being supported (userWorkspace, devServer), even if it means recreating a new config file or amending dynamically in some way to make it as explicit as possible, since we should make sure that if the user provides supported configuration items

  • sourceDirectory
  • devServer
  • etc

That we call those out specifically in our testing somehow, like though

  • return value of initConfig
  • output of file paths after running build
  • webpack config?

Although some cases may have some overlap, I think it's OK if it means that we can define and test the critical paths and outputs of a given feature / workflow / etc for the project if it ensures our tests can be as faithful to how a user would also setup / run our code. Providing a great and accurate development / testing environment we'll provide us much more grounded and confident in the usefulness and coverage of our tests.

A little duplication for the sake of clarity and setup is a positive tradeoff / investment in my book 👍

I always like to think to myself, the convenience in making build tools is to the user, not the maintainer.

Copy link
Member

@thescientist13 thescientist13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is pretty much there. Just a little input validation and then I think this should be good to merge. ✅

packages/cli/lib/config.js Outdated Show resolved Hide resolved
README.md Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
@@ -23,7 +23,7 @@ module.exports = readAndMergeConfig = async() => {
const userCfgFile = require(path.join(process.cwd(), 'greenwood.config.js'));

// prepend paths with current directory
if (userCfgFile.source) {
if (userCfgFile.source && !path.isAbsolute(userCfgFile.source)) {
Copy link
Member

@thescientist13 thescientist13 Apr 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be good to be a little more robust in our error handling here, since we're not checking that any of these directories exist first, which I think is important given how fundamental this particular option is to bootstrapping the entire tool.

Also, we can provide direct feedback to the user too. ex.

if (userCfgFile.workspace) {
  const workspace = userCfgFile.workspace;

  // 1) is it an absolute path that exists?  let's just use it
  // 2) else if it exists in process.cwd(), then use that
  // 3) else, give up and error out to the user
  if(path.isAbsolute(workspace) && fs.existsSync(workspace) {
    config.workspace = workspace;
  } else if(fs.existsSync(path.join(process.cwd(), workspace))) {
    path.join(process.cwd(), workspace)
  } else {
    // sorry, we couldn't find your workspace :(
    // common issues to check might be:
    // - typo in your workspace directory name, or in greenwood.config.js
    // - if using relative paths, make sure your workspace is in the same cwd as _greenwood.config.js_
    // - consider using an absolute path, e.g. path.join(__dirname, 'my', 'custom', 'path') // <__dirname>/my/custom/path/
    // reject()
  }
}

README.md Show resolved Hide resolved
@thescientist13
Copy link
Member

thescientist13 commented Apr 28, 2019

Almost there!! 🏁 🏃

Copy link
Member

@thescientist13 thescientist13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a www folder with nothing in it, created a greenwood.config.js in the root

module.exports = {
  workspace: 'www',
  publicPath: '/owen',
  devServer: {
    port: 1981,
    host: 'localhost'
  }
};

Looking good!

$ yarn build
yarn run v1.12.3
$ node ./packages/cli/index.js build
-------------------------------------------------------
Welcome to Greenwood ♻️
-------------------------------------------------------
Reading project config
Initializing project workspace contexts
Generating graph of workspace files...
Scaffolding out project files...
Generate pages from templates...
Writing imports for md...
Writing Lit routes...
setup index page and html
Scaffolding complete.
Building project for production.
Building SPA from compilation...
webpack build complete
...................................
Static site generation complete!
...................................
✨  Done in 11.52s.
Owens-MBP-2:greenwood owenbuckley$ yarn devel
Owens-MBP-2:greenwood owenbuckley$ cat public/index.html
<!DOCTYPE html><html lang="en" prefix="og:http://ogp.me/ns#"><head><style>body {transition: opacity ease-in 0.2s; }
body[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; }
</style>
    <title>My App</title>

    <meta charset="utf-8">
    <meta name="description" content="Greenwood">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="description" content="A modern and performant static site generator supporting Web Component based development.">
   
   .
   .
   .
    <eve-app></eve-app>

  <script type="text/javascript" src="/owen/index.861cad5fddf22530b046.bundle.js"></script>
$ yarn develop
yarn run v1.12.3
$ yarn clean && node ./packages/cli/index.js develop
$ rimraf ./.greenwood && rimraf ./public
-------------------------------------------------------
Welcome to Greenwood ♻️
-------------------------------------------------------
Reading project config
Initializing project workspace contexts
Generating graph of workspace files...
Scaffolding out project files...
Generate pages from templates...
Writing imports for md...
Writing Lit routes...
setup index page and html
Scaffolding complete.
Starting local development server
.
.
.
ℹ 「wdm」: Compiled successfully.
Directory www has been added
Now serving Development Server available at http://localhost:1981

Docs look good too!

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RFC Proposal and changes to workflows, architecture, APIs, etc
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[RFC] Public API / Userland Config
2 participants